How to run a frontend and an API together in one coding challenge
Questo articolo non è ancora tradotto, quindi è mostrato in inglese.
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:
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: apiHow the services start
- Databases start first.
- The top-level
installruns once at the workspace root, if there is one. - Each service runs its own
installcommands in itsworkDir, then itsdevcommand.
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:
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: webPorts
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:
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.