Ulabase

Start

Build a full-stack product catalog

The backend of a product catalog on Ulabase, request by request: collections, sample data, a public read permission and queries. Then a starter for the frontend.

This page builds the backend of a product catalog on your service, then points you to a starter for the frontend. It takes under ten minutes, and every request is written out so you see what the app does.

Tip
For a real app, fork a starter. Each one has sign-up, login, OAuth, invitations and teams already working, and a ulabase.setup.ts that configures your service in one command: React, Angular, Vue, Ecommerce with catalog, cart and Stripe checkout. This page explains the calls a starter makes.

What you will build

  • Products, categories and inventory as collections, with sample data.

  • Search by name, filter by price and category, from the API.

  • A permission that lets anyone read the products without signing in.

  • A frontend from a starter, in React, Angular or Vue.

Part 1: the backend

The short way: a starter and its setup file

The steps below build the catalog request by request. To have a working shop at once instead, take the ecommerce starter: its ulabase.setup.ts, in the root of the repository, configures your service in one command, with the catalog, a public read permission on it, the orders, and sign-up and login.

git clone https://github.com/ulabase/starter-ecommerce.git
cd starter-ecommerce
npm install                    # also installs the setup file's dependencies
npm install -g ulabase         # the ulabase command
ulabase login                  # asks for a personal access token: your profile, Tokens
ulabase setup --srv <srvId>

ulabase setup reads ulabase.setup.ts from the folder you run it in. Each step checks first and writes only what is missing, so running it again changes nothing. Its last step writes the service URL into src/environments/environment.ts, so npm run dev opens a shop on your service. The shop then works with its catalog, cart and accounts; checkout turns on when you add the Stripe test keys and run the setup again, as the starter’s README explains. See ulabase for the command, and Tokens for the personal access token.

Step 1: create the collections

Create the collections for products, categories and inventory:

export ROOT_PASSWORD='your root password'

# Create collections
curl -i -X PUT https://c0ffee.ulabase.app/products \
  -u root:$ROOT_PASSWORD
curl -i -X PUT https://c0ffee.ulabase.app/categories \
  -u root:$ROOT_PASSWORD
curl -i -X PUT https://c0ffee.ulabase.app/inventory \
  -u root:$ROOT_PASSWORD

# Create permission for unauthenticated read access to products
curl -i -X POST https://c0ffee.ulabase.app/acl \
  -u root:$ROOT_PASSWORD -H "Content-Type: application/json" \
  -d '{
    "_id": "allCanGetProducts",
    "roles": ["$unauthenticated"],
    "predicate": "path(/products) and method(GET)",
    "priority": 100
  }'
export ROOT_PASSWORD='your root password'

# Create collections
http PUT https://c0ffee.ulabase.app/products \
  -a root:$ROOT_PASSWORD
http PUT https://c0ffee.ulabase.app/categories \
  -a root:$ROOT_PASSWORD
http PUT https://c0ffee.ulabase.app/inventory \
  -a root:$ROOT_PASSWORD

# Create permission for unauthenticated read access to products
http POST https://c0ffee.ulabase.app/acl \
  -a root:$ROOT_PASSWORD \
  Content-Type:application/json \
  _id="allCanGetProducts" \
  roles:='["$unauthenticated"]' \
  predicate="path(/products) and method(GET)" \
  priority:=100
// Create collections
const createCollections = async () => {
  const headers = {
    'Authorization': 'Basic ' + btoa('root:' + process.env.ROOT_PASSWORD)
  };

  fetch('https://c0ffee.ulabase.app/products', {
    method: 'PUT',
    headers
  })
  .then(response => {
    if (response.ok) {
      console.log('Products collection created successfully');
    } else {
      console.error('Failed to create products collection:', response.status);
    }
  })
  .catch(error => console.error('Error:', error));

  fetch('https://c0ffee.ulabase.app/categories', {
    method: 'PUT',
    headers
  })
  .then(response => {
    if (response.ok) {
      console.log('Categories collection created successfully');
    } else {
      console.error('Failed to create categories collection:', response.status);
    }
  })
  .catch(error => console.error('Error:', error));

  fetch('https://c0ffee.ulabase.app/inventory', {
    method: 'PUT',
    headers
  })
  .then(response => {
    if (response.ok) {
      console.log('Inventory collection created successfully');
    } else {
      console.error('Failed to create inventory collection:', response.status);
    }
  })
  .catch(error => console.error('Error:', error));
};

// Create permission for unauthenticated read access to products
const createProductsReadPermission = () => {
  fetch('https://c0ffee.ulabase.app/acl', {
    method: 'POST',
    headers: {
      'Authorization': 'Basic ' + btoa('root:' + process.env.ROOT_PASSWORD),
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      _id: "allCanGetProducts",
      roles: ["$unauthenticated"],
      predicate: "path(/products) and method(GET)",
      priority: 100
    })
  })
  .then(response => {
    if (response.ok) {
      console.log('Products read permission created successfully');
    } else {
      console.error('Failed to create products read permission:', response.status);
    }
  })
  .catch(error => console.error('Error:', error));
};

// Execute
createCollections();
createProductsReadPermission();

Step 2: add sample data

Download the sample data files and import them into your service.

Download the sample data

# Download categories
curl -o categories.json https://ulabase.com/assets/examples/categories.json

# Download products
curl -o products.json https://ulabase.com/assets/examples/products.json

Import the data into your service:

# Import categories
curl -X POST https://c0ffee.ulabase.app/categories \
  -u root:$ROOT_PASSWORD \
  -H "Content-Type: application/json" \
  -d @categories.json

# Import products
curl -X POST https://c0ffee.ulabase.app/products \
  -u root:$ROOT_PASSWORD \
  -H "Content-Type: application/json" \
  -d @products.json
# Import categories
http POST https://c0ffee.ulabase.app/categories \
  -a root:$ROOT_PASSWORD \
  Content-Type:application/json < categories.json

# Import products
http POST https://c0ffee.ulabase.app/products \
  -a root:$ROOT_PASSWORD \
  Content-Type:application/json < products.json
const fs = require('fs');

// Read and import categories
const importCategories = async () => {
  try {
    const categories = JSON.parse(fs.readFileSync('categories.json', 'utf8'));

    const response = await fetch('https://c0ffee.ulabase.app/categories', {
      method: 'POST',
      headers: {
        'Authorization': 'Basic ' + btoa('root:' + process.env.ROOT_PASSWORD),
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(categories)
    });

    if (response.ok) {
      console.log('Categories imported successfully');
    } else {
      console.error('Failed to import categories:', response.status);
    }
  } catch (error) {
    console.error('Error importing categories:', error);
  }
};

// Read and import products
const importProducts = async () => {
  try {
    const products = JSON.parse(fs.readFileSync('products.json', 'utf8'));

    const response = await fetch('https://c0ffee.ulabase.app/products', {
      method: 'POST',
      headers: {
        'Authorization': 'Basic ' + btoa('root:' + process.env.ROOT_PASSWORD),
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(products)
    });

    if (response.ok) {
      console.log('Products imported successfully');
    } else {
      console.error('Failed to import products:', response.status);
    }
  } catch (error) {
    console.error('Error importing products:', error);
  }
};

// Execute imports
(async () => {
  await importCategories();
  await importProducts();
  console.log('Data import completed!');
})();

Step 3: test your API

Try a few queries to check that everything works:

# Search products by name
curl -i "https://c0ffee.ulabase.app/products" \
  -u root:$ROOT_PASSWORD \
  -G --data-urlencode 'filter={"name":{"$regex":"headphones","$options":"i"}}'

# Filter by price range
curl "https://c0ffee.ulabase.app/products" \
  -u root:$ROOT_PASSWORD \
  -G --data-urlencode 'filter={"price":{"$gte":50,"$lte":150}}'

# Get products with low inventory
curl "https://c0ffee.ulabase.app/products" \
  -u root:$ROOT_PASSWORD \
  -G --data-urlencode 'filter={"quantity":{"$lt":10}}'

# Category-based filtering with sorting
curl "https://c0ffee.ulabase.app/products" \
  -u root:$ROOT_PASSWORD \
  -G --data-urlencode "filter={'category':'electronics'}" \
  -G --data-urlencode "sort={'price':1}"
# Search products by name
http GET https://c0ffee.ulabase.app/products \
  -a root:$ROOT_PASSWORD \
  filter=="{'name':{\$regex:'headphones',\$options:'i'}}"

# Filter by price range
http GET https://c0ffee.ulabase.app/products \
  -a root:$ROOT_PASSWORD \
  filter=="{'price':{\$gte:50,\$lte:150}}"

# Get products with low inventory
http GET https://c0ffee.ulabase.app/products \
  -a root:$ROOT_PASSWORD \
  filter=="{'quantity':{\$lt:10}}"

# Category-based filtering with sorting
http GET https://c0ffee.ulabase.app/products \
  -a root:$ROOT_PASSWORD \
  filter=="{'category':'electronics'}" sort=="{price:1}"
// Search products by name
const searchByName = () => {
  const filter = encodeURIComponent("{'name':{\$regex:'headphones',\$options:'i'}}");
  fetch(`https://c0ffee.ulabase.app/products?filter=${filter}`, {
    headers: {
      'Authorization': 'Basic ' + btoa('root:' + process.env.ROOT_PASSWORD)
    }
  })
  .then(response => response.json())
  .then(data => {
    console.log('Search results:', data);
  })
  .catch(error => console.error('Error:', error));
};

// Filter by price range
const filterByPriceRange = () => {
  const filter = encodeURIComponent("{'price':{\$gte:50,\$lte:150}}");
  fetch(`https://c0ffee.ulabase.app/products?filter=${filter}`, {
    headers: {
      'Authorization': 'Basic ' + btoa('root:' + process.env.ROOT_PASSWORD)
    }
  })
  .then(response => response.json())
  .then(data => {
    console.log('Price range results:', data);
  })
  .catch(error => console.error('Error:', error));
};

// Get products with low inventory
const getLowInventory = () => {
  const filter = encodeURIComponent("{'quantity':{\$lt:10}}");
  fetch(`https://c0ffee.ulabase.app/products?filter=${filter}`, {
    headers: {
      'Authorization': 'Basic ' + btoa('root:' + process.env.ROOT_PASSWORD)
    }
  })
  .then(response => response.json())
  .then(data => {
    console.log('Low inventory products:', data);
  })
  .catch(error => console.error('Error:', error));
};

// Category-based filtering with sorting
const filterByCategory = () => {
  const filter = encodeURIComponent("{'category':'electronics'}");
  const sort = encodeURIComponent("{price:1}");
  fetch(`https://c0ffee.ulabase.app/products?filter=${filter}&sort=${sort}`, {
    headers: {
      'Authorization': 'Basic ' + btoa('root:' + process.env.ROOT_PASSWORD)
    }
  })
  .then(response => response.json())
  .then(data => {
    console.log('Category results:', data);
  })
  .catch(error => console.error('Error:', error));
};

// Execute
searchByName();
filterByPriceRange();
getLowInventory();
filterByCategory();

Part 2: the frontend

With the backend ready, build the frontend.

Choose a starter

For an application you intend to keep, fork a starter. Each is a base for a real app: signup, login, OAuth, email verification, password reset, team invitations and a team switcher already work, and each has a ulabase.setup.ts that configures the service to match.

Starter What it is

React

Auth and multi-tenancy, on @ulabase/kit-react

Angular

The same, on @ulabase/kit-ng, with SSR for public routes

Vue

The same, on @ulabase/kit-vue

Ecommerce (React)

The above plus a shop: catalog, cart, Stripe Checkout, order ledger, guest checkout

The setup file reads the app’s feature flags, so the app and the service stay in step.

All four use the Ulabase Kit for sign-up, sign-in, teams and payments, and ulabase configures their service from a ulabase.setup.ts committed with the code.

Run the ecommerce starter

The ecommerce starter is this page’s catalog grown into a shop:

git clone https://github.com/ulabase/starter-ecommerce.git
cd starter-ecommerce
npm install

Then ulabase setup --srv <srvId>, which also writes the service URL into the app, and npm run dev. Its README has the details, Stripe included.

Learn more

Troubleshooting

The frontend cannot reach the service

  • Check apiUrl in the app’s environment file: ulabase setup --srv <srvId> writes it

  • Check that the permission for unauthenticated reads exists

  • Check in the console that the service is active

  • Look for CORS errors in the browser console

No products show

  • Check that you imported the sample data (Step 2)

  • Check that the products collection exists: curl https://c0ffee.ulabase.app/products

  • Check that the permission allows unauthenticated GET requests to /products

Search or filter does not work

  • Check the syntax of your MongoDB queries

  • Check that the filter parameters are URL-encoded

  • Run the same query with cURL or HTTPie

  • Look for JavaScript errors in the browser console

Support

For help, open a ticket from the Support page of your service. The answer arrives by email and stays under the ticket.