NewLive coding: one link for the candidate, one shared sandbox for everyone.See it ↓
KendorDocs

How to run a frontend and an API together in one coding challenge

For challenge authors ·

Define each part as a service in kendor.yaml with its own folder, commands and port, pick the preview service, and route paths like /api to the API.

A challenge can run several processes side by side, such as a React frontend and an API. Give each one an entry under services in kendor.yaml with its folder (workDir), commands and port, name the one the preview shows in preview.service, and add routing so requests to /api reach the API while everything else reaches the frontend. Every service runs in the same sandbox.

A complete example

A repository with a Vite frontend in web/ and an Express API in api/, backed by PostgreSQL:

kendor.yaml
version: 1
runtime:
  type: node
  version: "22"
databases:
  - type: postgres
    version: "18"
    init: api/db/seed.sql
services:
  api:
    workDir: api
    install:
      commands:
        - npm ci
    dev:
      command: node --watch src/server.js
      port: 3000
      env:
        vars:
          APP_URL: routing.web.url
    test:
      command: node --test
  web:
    workDir: web
    install:
      commands:
        - npm ci
    dev:
      command: npm run dev -- --port $PORT
      port: 5173
      env:
        vars:
          VITE_API_URL: routing.api.url
    publicEnvPrefix: VITE_
preview:
  service: web
routing:
  - path: /api
    service: api
  - path: /
    service: web
kendor:
  gradeService: api

How the services start

  1. Databases start first.
  2. The top-level install runs once at the workspace root, if there is one.
  3. Each service runs its own install commands in its workDir, then its dev command.

Each service gets its own process window, named after the service, and shows up in the Processes view where the candidate can stop, start and restart it.

workDir

workDir is the folder a service's install, dev and test commands run in, relative to the workspace root. Leave it out for a service at the root. It can't start with / or contain ...

Install once or per service

For separate packages, put npm ci (or pip install, composer install) in each service's install. For a monorepo with one lockfile at the root, such as a pnpm workspace, use the top-level install instead, so dependencies install once:

kendor.yaml
version: 1
runtime:
  type: node
  version: "22"
install:
  commands:
    - pnpm install --frozen-lockfile
services:
  api:
    workDir: apps/api
    dev:
      command: pnpm dev
      port: 3000
  web:
    workDir: apps/web
    dev:
      command: pnpm dev --port $PORT
      port: 5173
preview:
  service: web
routing:
  - path: /api
    service: api
  - path: /
    service: web

Ports

Give every service that serves HTTP a dev.port. Kendor sets PORT to it and HOST to 0.0.0.0 for that service's dev command, so --port $PORT keeps the two in sync. Two services can't use the same port.

The preview and routing

preview.service is the service the candidate's preview pane opens. It's required as soon as there is more than one service.

routing maps path prefixes on the preview address to services. The longest matching prefix wins, so with the routes above, /api/products goes to api and every other path goes to web. The path isn't rewritten: the API receives /api/products, so mount its routes under /api. Because the frontend and the API share one address, the browser makes same-origin requests and there's no CORS to set up.

While any routed service is still starting, for example while the API runs its migrations, the preview shows a short waiting page instead of loading a frontend whose first API calls would fail.

Any service with a port can also be opened on its own address from the Processes view.

Pass one service's URL to another

A service shouldn't hardcode localhost URLs for the browser, because the browser reaches the sandbox through its preview address. Use a reference in dev.env.vars instead:

Reference Resolves to
routing.api.url The preview address plus the API's route, for example https://<preview-address>/api
routing.web.url The preview address itself, since web is routed at /. The API above uses it as APP_URL, for links it builds back to the frontend.
databases.postgres.url The PostgreSQL connection URL
lit:<text> Fixed text

Kendor resolves these before any service starts, so the order the services come up in doesn't matter. A reference to a service that doesn't exist fails validation when you save.

For server-to-server calls inside the sandbox, http://localhost:3000 still works.

publicEnvPrefix

publicEnvPrefix records which variables are meant for browser code, such as VITE_ for Vite. Kendor stores it but doesn't act on it today; Vite itself only exposes VITE_ variables to the bundle.

Vite frontends

Vite blocks requests for host names it doesn't know, which includes the sandbox's preview address. The @kendordev/vite plugin fixes that and points hot reload at the right address. It only acts inside a Kendor sandbox, so it's safe to commit.

When you upload a zip or import a repository with a vite.config at the root, Kendor adds the plugin for you. For a Vite app in a subfolder, such as web/ above, add it yourself:

web/vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import kendor from '@kendordev/vite';

export default defineConfig({
  plugins: [react(), kendor()],
});

Install it with npm install -D @kendordev/vite and commit the updated lockfile.

Tests across services

The Tests tab runs the preview service's test.command by default. When the tests live in another service, as with api above, set kendor.gradeService to that service. To run the tests of several services, list them in kendor.gradeServices; each runs in its own workDir and the results appear as service › test.

Was this article helpful?