Microservices Part 1: Deploy a Frontend, Two APIs and a Database

Part 1 of Microservices on Light Cloud. Three services from one repository, one PostgreSQL database, no YAML.

Microservices Part 1: Deploy a Frontend, Two APIs and a Database
On this pageShow
  1. What you will build
  2. Before you start
  3. Step 1: Fork the repository
  4. Step 2: Create a folder for the project
  5. Step 3: Create the PostgreSQL database
  6. Step 4: Copy the connection string
  7. Step 5: Deploy catalog-api
  8. Step 6: Check that catalog-api works
  9. Step 7: Deploy orders-api
  10. Step 8: Deploy the web frontend
  11. Step 9: Allow the frontend to call the APIs
  12. Step 10: Place an order
  13. Troubleshooting
  14. FAQ
  15. Next steps

To deploy microservices on Light Cloud, put each service in its own folder of one GitHub repository, create one Light Cloud app per folder by setting its Root directory, and connect the services with environment variables that hold each other's addresses. Light Cloud detects the framework in every folder, builds it, and gives each service its own HTTPS address. No Kubernetes, no Dockerfiles and no YAML.

This is Part 1 of a six-part series. By the end of it you have a small online shop running as three services and a database. Later parts add autoscaling, cold-start tuning, branch environments and request tracing to the same app.

What you will build

Bean There, a tiny coffee shop made of four parts:

  • web: a React (Vite) shop front, served from the edge as a static site.
  • catalog-api: a Node.js (Express) service that owns products and stock.
  • orders-api: a Python (FastAPI) service that owns orders. To place an order it asks catalog-api to reserve stock first.
  • bean-there-db: one PostgreSQL database. Each API owns its own table.

The finished project in the Light Cloud console: catalog-api, orders-api and web deployed, and bean-there-db ready

Live demo: main-web-examples.light-cloud.io. Source code: github.com/light-cloud-com/tutorial-microservices, tag part-1.

The repository has one folder per service:

text
tutorial-microservices/
  web/           React (Vite)       static site
  catalog-api/   Node.js (Express)  container
  orders-api/    Python (FastAPI)   container
  README.md

Before you start

  • A GitHub account.
  • A Light Cloud account on the Starter plan or higher. The free Hobby plan allows one service and no databases; this project needs two API services and a database.
  • The Light Cloud GitHub App connected to your account. If it is not, the console asks you to connect it the first time you choose Deploy from GitHub.
  • Optional, to test from a terminal: macOS and Linux have curl and OpenSSL built in. On Windows, use PowerShell 7 (winget install Microsoft.PowerShell); the Windows tabs use curl.exe, which ships with Windows 10 and 11. Pick your system on any command below and the page remembers it.

Step 1: Fork the repository

Light Cloud deploys from a repository you own, so start by making your own copy.

  1. Open github.com/light-cloud-com/tutorial-microservices.
  2. Click Fork, keep the name tutorial-microservices, and click Create fork.

tutorial-microservices now appears under your own GitHub account.

Step 2: Create a folder for the project

A folder keeps the four resources of this project together in the console sidebar.

  1. In the Light Cloud console, click New... at the top of the sidebar.
  2. Click New Folder.

The New menu in the Light Cloud sidebar with New Folder at the bottom

  1. Type bean-there as the Name and click Create folder.

The Create a folder dialog with the name bean-there and the Create folder button highlighted

You should see bean-there in the sidebar.

Step 3: Create the PostgreSQL database

Both APIs store their data in one database, so create it first.

  1. Hover over bean-there in the sidebar and click the + button next to it.
  2. Click New Database.

The bean-there folder menu with New Database highlighted

  1. Under Engine, click PostgreSQL.

The Create page with the Database tile selected and the PostgreSQL engine highlighted

  1. Change the Name to bean-there-db.
  2. Open Size and choose Dev, the shared instance. It is ready in seconds and is enough for this tutorial.

The Size dropdown open with Dev, Shared instance highlighted, and Starter and Pro below it

  1. Leave Storage at 1 GB and pick the Region closest to you. Click Create database.

The database form filled in with the name bean-there-db and the Create database button highlighted

You should see the database page with a green Ready badge. In my run it took about six seconds.

The bean-there-db overview page with the Ready badge highlighted

Step 4: Copy the connection string

The connection string is the address and password your services use to reach the database.

  1. Open the Credentials tab.
  2. Under Connection String, click Copy. Paste it somewhere private for the next steps; you will use it twice.

The Connection String card with the password hidden and the Copy button highlighted

The copied string looks like this. Your user name, password and database name are in it; here they are hidden behind stars:

text
postgresql://u_******:********@bean-there-db-yourworkspace.db.light-cloud.io:5432/db******

The database only accepts encrypted connections, and this string does not say so yet. You will add a short ending to it for each service in the next steps.

Step 5: Deploy catalog-api

catalog-api is the first service because orders-api needs its address.

  1. Click + next to bean-there again and choose Deploy from GitHub.

The bean-there folder menu with Deploy from GitHub highlighted

  1. In Search your repositories..., type tutorial-microservices and click your fork.

The repository search showing tutorial-microservices highlighted

Light Cloud now looks at the root of the repository and shows "I're not sure what this is". That is expected: the root holds three apps, not one. You tell it which folder to use.

The warning I're not sure what this is, with the folder button next to Root directory highlighted

  1. Click the folder button next to Root directory and choose catalog-api.

The folder picker listing catalog-api, orders-api and web, with catalog-api highlighted

The banner turns green: Express / Backend, based on package.json. Light Cloud found Express in the dependencies, so it runs this folder as a server.

  1. The Name field still says tutorial-microservices. Change it to catalog-api. The name becomes part of the service's address.

The detection banner Express Backend, with Name set to catalog-api and Root directory set to catalog-api

  1. Click Advanced - build settings, environment variables, domain, scaling. Port is already 8080; leave the rest as it is.
  2. Under Environment variables, click Add variable twice and fill in:
KEYvalue
DATABASE_URLyour connection string, followed by ?sslmode=require&uselibpqcompat=true
INTERNAL_SECRETa long random string; keep it, orders-api needs the same one

Put together, the two values look like this (stars hide your password and names):

text
DATABASE_URL     postgresql://u_******:********@bean-there-db-yourworkspace.db.light-cloud.io:5432/db******?sslmode=require&uselibpqcompat=true
INTERNAL_SECRET  cf91d1e26971527e2ec7362e9452f5abb4670e767ab1a0af

To make a random secret, run the command below. You should see one line of 48 random letters and digits; yours will be different:

terminal
$ openssl rand -hex 24
cf91d1e26971527e2ec7362e9452f5abb4670e767ab1a0af

The Environment variables section with DATABASE_URL and INTERNAL_SECRET added and their values hidden

The ending ?sslmode=require&uselibpqcompat=true tells the Node.js pg driver to use an encrypted connection the way the PostgreSQL command-line tools do. Without it, catalog-api cannot connect to the database.

  1. Click Deploy.

The catalog-api environments page with Production showing Deploying

You should see the Production environment move from Deploying to Deployed. In my run that took about 75 seconds.

Step 6: Check that catalog-api works

A quick request confirms the service is up and can read the database.

Your service's address follows the pattern https://main-<app name>-<workspace>.light-cloud.io. You can also copy it from the URL card on the environment's Overview tab.

The catalog-api Overview tab with the Deployed badge and the URL card highlighted

Ask it for its products. You should see the four products catalog-api created on its first start, on one line:

terminal
$ curl https://main-catalog-api-yourworkspace.light-cloud.io/products
[{"id":1,"name":"Espresso beans, 1 kg","price_cents":2400,"stock":40},{"id":2,"name":"Pour-over kettle","price_cents":5900,"stock":12},{"id":3,"name":"Ceramic mug","price_cents":1500,"stock":100},{"id":4,"name":"Paper filters, 100 pack","price_cents":600,"stock":250}]

Now check that the internal route refuses callers without the secret. You should see the status line HTTP/2 401 and the error body (other headers left out here):

terminal
$ curl -i -X POST https://main-catalog-api-yourworkspace.light-cloud.io/internal/products/1/reserve \
  -H "content-type: application/json" -d '{"quantity":1}'
HTTP/2 401
content-type: application/json; charset=utf-8
x-powered-by: Express

{"error":"Unauthorized"}

Every Light Cloud address is public, so this check is what keeps strangers from changing your stock. This is the part that does it:

catalog-api/server.js
javascript
// Only other services may call /internal routes.
function requireInternalSecret(req, res, next) {
  if (!INTERNAL_SECRET || req.get("x-internal-secret") !== INTERNAL_SECRET) {
    return res.status(401).json({ error: "Unauthorized" });
  }
  next();
}

Step 7: Deploy orders-api

orders-api follows the same steps, with its own folder and one extra variable.

  1. Click + next to bean-there, choose Deploy from GitHub, and pick your fork again.
  2. Set Root directory to orders-api and change Name to orders-api.

The banner reads FastAPI / Backend, based on requirements.txt. Light Cloud runs FastAPI with uvicorn on port 8000; you do not write a start command.

The detection banner FastAPI Backend for the orders-api folder

  1. Open Advanced and add three variables:
KEYvalue
DATABASE_URLyour connection string, followed by ?sslmode=require
INTERNAL_SECRETthe same secret you gave catalog-api
CATALOG_API_URLcatalog-api's address, for example https://main-catalog-api-yourworkspace.light-cloud.io

For example:

text
DATABASE_URL     postgresql://u_******:********@bean-there-db-yourworkspace.db.light-cloud.io:5432/db******?sslmode=require
INTERNAL_SECRET  cf91d1e26971527e2ec7362e9452f5abb4670e767ab1a0af
CATALOG_API_URL  https://main-catalog-api-yourworkspace.light-cloud.io

The Environment variables section of orders-api with DATABASE_URL, INTERNAL_SECRET and CATALOG_API_URL

Python's psycopg driver only needs ?sslmode=require. The longer ending in Step 5 is specific to Node.js.

  1. Click Deploy.

This is how orders-api uses those variables when a customer buys something:

orders-api/main.py
python
async with httpx.AsyncClient(timeout=10) as client:
    reply = await client.post(
        f"{CATALOG_API_URL}/internal/products/{order.product_id}/reserve",
        json={"quantity": order.quantity},
        headers={"x-internal-secret": INTERNAL_SECRET, "x-request-id": request_id},
    )

When it is deployed, check it. You should see:

terminal
$ curl https://main-orders-api-yourworkspace.light-cloud.io/health
{"status":"ok","service":"orders-api"}

Step 8: Deploy the web frontend

The shop front is a static site, so it needs both API addresses at build time.

  1. Click + next to bean-there, choose Deploy from GitHub, and pick your fork.
  2. Set Root directory to web and change Name to web.

The banner reads React / Frontend and the card says Served from the edge: the built files go to a content delivery network, not to a server.

The detection banner React Frontend with Served from the edge highlighted

  1. Open Advanced. Light Cloud has already filled in npm ci, npm run build and the output folder dist. Add two variables:
KEYvalue
VITE_CATALOG_API_URLcatalog-api's address
VITE_ORDERS_API_URLorders-api's address

The web app's Environment variables with VITE_CATALOG_API_URL and VITE_ORDERS_API_URL

Vite copies variables that start with VITE_ into the JavaScript bundle during the build. That is why they must be set before the first deploy.

  1. Click Deploy.

Step 9: Allow the frontend to call the APIs

Open the web address, for example https://main-web-yourworkspace.light-cloud.io. The page loads, but the product list stays empty.

The Bean There page loaded with no products and an error message

The browser blocked the calls because the APIs only allow http://localhost:5173, the address used during local development. This rule is called CORS (cross-origin resource sharing): a browser only lets a page on one address read answers from another address if that address says yes. Both APIs read the allowed address from WEB_ORIGIN:

catalog-api/server.js
javascript
app.use(cors({ origin: WEB_ORIGIN }));

Set it on catalog-api:

  1. In the sidebar, open catalog-api, then Production, then the Settings tab.
  2. In Environment Variables, click Edit, then Add.
  3. Enter WEB_ORIGIN as the key and your web address as the value, without a slash at the end.
  4. Click Save.

The Environment Variables editor with WEB_ORIGIN added and the Save button highlighted

You should see "Environment variables saved". Saving redeploys the service by itself; there is no separate deploy button to press.

Repeat the same four steps for orders-api.

Step 10: Place an order

Reload the shop about a minute after saving. The products appear, each with a Buy button.

Click Buy on any product. You should see "Order #1 placed", the stock go down by one, and the order appear under Latest orders.

The live Bean There shop with the message Order #1 placed: Pour-over kettle

That one click went through all four parts: the browser called orders-api, orders-api asked catalog-api to reserve stock with the shared secret, catalog-api updated the products table, and orders-api saved the order in the orders table.

Troubleshooting

I're not sure what this is

You picked the repository but not a folder. Set Root directory to catalog-api, orders-api or web, and the detection banner turns green.

Access to fetch has been blocked by CORS policy

The full message starts with Access to fetch at 'https://main-catalog-api-...' from origin 'https://main-web-...' has been blocked by CORS policy. The API's WEB_ORIGIN is missing or does not match the web address exactly. Check for https://, and make sure there is no slash at the end. Saving the variable redeploys the service; wait about a minute and reload.

catalog-api fails to start with a certificate or SSL error

The DATABASE_URL on catalog-api is missing the ending ?sslmode=require&uselibpqcompat=true. Add it in Settings, Environment Variables, and save.

Placing an order says "Catalog service unavailable"

orders-api could not reserve stock. Either CATALOG_API_URL points to the wrong address, or INTERNAL_SECRET is not the same on both services. Check both, then save.

The product list is empty and there is no CORS error

The web app was built before VITE_CATALOG_API_URL and VITE_ORDERS_API_URL were set. Add them in the web app's Settings; saving rebuilds it with the new values.

FAQ

Do I need Kubernetes or Docker to run microservices on Light Cloud?

No. Light Cloud detects each service from its files (package.json, requirements.txt) and builds the container for you. A Dockerfile is optional.

Can my services talk to each other over a private network?

Not today. Services call each other over their public HTTPS addresses, so internal routes need their own check. This tutorial uses a shared secret header.

Why do both APIs use the same database?

To keep the tutorial cheap and simple. Each service owns its own table and never reads the other's, so you can split them into two databases later without changing the code.

Does it cost money when nobody is using the shop?

The APIs start with Min instances set to 0, so they scale to zero when idle. The plan price includes usage worth that price each month.

Can I write the services in other languages?

Yes. Each folder is detected on its own, so a Go, Java or .NET service can sit next to these two in the same repository.

Next steps

  • Part 2: how the services find each other, and a closer look at service addresses, CORS and the shared secret.
  • Part 3: autoscaling explained with a load test.
  • Want the one-service version first? Deploy your app by asking Claude Code.