Backend from Scratch
This is the smallest useful backend shape for W7S.
Repo structure
my-app/
backend/
src/
health.ts
notes.ts
db/
app/
migrations/
0001_init.sql
w7s.json
Define routes
Create w7s.json:
[
{
"type": "webhook",
"path": "/health",
"method": "GET",
"handler": "./backend/src/health.ts",
"auth": "none"
},
{
"type": "webhook",
"path": "/notes",
"method": "ANY",
"handler": "./backend/src/notes.ts",
"auth": "none"
}
]
Add a health handler
export default async function handle() {
const deployMeta =
(globalThis as { __W7S_DEPLOY_META?: Record<string, unknown> }).__W7S_DEPLOY_META ?? {};
return {
status: 200,
body: {
ok: true,
service: "my-app",
branch: deployMeta.branch ?? null,
commit: deployMeta.commitHash ?? null,
},
};
}
Add a database migration
Create db/app/migrations/0001_init.sql:
create table if not exists notes (
id integer primary key autoincrement,
title text not null,
body text,
created_at_ms integer not null
);
Add a note handler
Use Drizzle rather than raw SQL in request handlers.
import { desc } from "drizzle-orm";
import { db, schema } from "./db/client";
export default async function handle(payload: {
method?: string;
body?: unknown;
}) {
const method = String(payload?.method ?? "GET").toUpperCase();
if (method === "GET") {
const rows = await db
.select()
.from(schema.notes)
.orderBy(desc(schema.notes.createdAtMs))
.limit(20);
return { status: 200, body: { ok: true, items: rows } };
}
if (method === "POST") {
const input = (payload.body ?? {}) as { title?: string; body?: string };
if (!input.title?.trim()) {
return { status: 400, body: { ok: false, error: "Missing title." } };
}
await db.insert(schema.notes).values({
title: input.title.trim(),
body: input.body?.trim() || null,
createdAtMs: Date.now(),
});
return { status: 201, body: { ok: true } };
}
return { status: 405, body: { ok: false, error: "Method not allowed." } };
}
Deploy workflow
Minimal backend-only deploy workflow:
name: Deploy Backend
on:
push:
branches: [main]
paths:
- "backend/**"
- "db/**"
- "w7s.json"
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- run: zip -qr repo.zip . -x ".git/*" -x ".github/*"
- run: |
OWNER_SLUG="$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')"
REPO_SLUG="$(basename "${{ github.repository }}")"
curl --fail --show-error \
-X POST "https://${OWNER_SLUG}.w7s.cloud/${REPO_SLUG}/_deploy?artifact=source&scope=backend" \
-H "Authorization: Bearer ${{ github.token }}" \
-H "x-github-repository: ${{ github.repository }}" \
-H "x-github-sha: ${{ github.sha }}" \
-H "x-github-branch: ${{ github.ref_name }}" \
-H "content-type: application/zip" \
--data-binary "@repo.zip"
Result
After deploy:
GET /healthconfirms the deploy is live- migrations are already applied
GET /notesandPOST /notesoperate on the repo-scoped database
For the DB layer, continue with Database and Drizzle.