Tutorial: from svs new to a deployed app
Dari docs/tutorial/first-app.md di repository framework.
Halaman ini belum diterjemahkan. Kamu sedang membaca versi bahasa Inggris.
This tutorial takes a new employee from an empty folder to a running container. It takes about 30 minutes. You build a small notes API with a database table, a validated route, a test, and a Docker image. Each code block below is type-checked against the framework in CI. The block marked as a test also runs.
1. Create the project
@samveys/* comes from the Samveys registry on registry.sadata.dev, which anyone can read: no account or token is needed. The project .npmrc that svs new writes already points there.
svs new notes --kit=api
cd notes
pnpm install
pnpm test # the generated GET /ping test passes with no edits
pnpm dev # svs serve --watch: http://localhost:3000/ping → pongWhat you got:
samveys.config.tswithhttp(),database()(SQLite instorage/app.db, the default engine),orm()andhealth().app/routes.tswith the routes.tests/with the tests.- A
Dockerfileand a CI workflow.
2. Add a model and its table
svs make:model Note --migrationGive the model its columns:
import { Model, col } from '@samveys/orm'
export class Note extends Model.define('notes', {
id: col.increments(),
title: col.string(120),
body: col.text().nullable(),
createdAt: col.timestamp().autoCreate(),
}) {}Then let the framework write the migration from the model, and run it:
svs make:migration create_notes --diff # compares app/models with the database
svs migrate3. Add a validated route
import { defineRoutes } from '@samveys/http'
import { schema } from '@samveys/validator'
import { Note } from './models/note.ts'
const NewNote = schema.object({ title: schema.string().min(1).max(120), body: schema.string().optional() })
export default defineRoutes((router) => {
router.get('/ping', () => 'pong')
router.get('/notes', async () => (await Note.query().orderBy('id', 'desc').limit(50).get()).map((note) => note.toJSON()))
// The body is validator output, so create() accepts it. Unknown fields are already gone.
router.post('/notes', { body: NewNote }, async ({ body, response }) => response.created((await Note.create(body)).toJSON()))
})A body that fails the schema gets 422 with the failing fields in errors, without any code from you.
4. Test it
import assert from 'node:assert/strict'
import { test } from 'node:test'
import { createDatabaseManager, defineDatabaseConfig, sqlite } from '@samveys/database'
import { http } from '@samveys/http'
import { useDatabase } from '@samveys/orm'
import { createTestApp } from '@samveys/testing'
import routes from '../app/routes.ts'
test('POST /notes stores a note, and a bad body is a 422', async () => {
const db = createDatabaseManager(defineDatabaseConfig({ default: 'main', connections: { main: sqlite({ file: ':memory:' }) } }))
await db.rawQuery('create table notes (id integer primary key autoincrement, title varchar(120) not null, body text, created_at timestamptz)')
useDatabase(db)
await using app = await createTestApp({ plugins: [http()], routes: (router) => routes.register(router) })
const created = await app.http.post('/notes', { json: { title: 'First', isAdmin: true } })
created.assertStatus(201)
assert.equal(created.json<{ title: string }>().title, 'First')
assert.equal('isAdmin' in created.json<object>(), false, 'unknown fields never reach the model')
const invalid = await app.http.post('/notes', { json: { title: '' } })
invalid.assertStatus(422)
assert.equal((await app.http.get('/notes')).json<unknown[]>().length, 1)
await db.close()
})pnpm test5. Check it before deploying
svs check --deploy # fails without APP_KEY, with debug on, or with an unreachable database
svs route:list6. Build and run the container
svs build --docker
docker build -t notes .
docker run --rm -p 3000:3000 -e APP_ENV=production -e APP_KEY=base64:… notes
curl localhost:3000/health/ready # 200 once the database answers- The image. It runs as the non-root
nodeuser and contains no.envfiles. All configuration comes from env vars. - Shutdown. On
SIGTERM,/health/readyturns 503 at once, and in-flight requests still finish. - A server database. To switch from SQLite, set
DB_CONNECTION=mysqlorDB_CONNECTION=postgreswith aDATABASE_URL. No code changes. See Choose a database engine.
Next
- Protect a browser app: sessions, CSRF and rate limits.
- Sign in with OIDC and Issue API tokens.
- Background work: queue and scheduler; search: search; realtime: SSE and WebSocket in http and broadcast.
- Every package: docs index.
- Deploy with Docker: migrations, health checks and shutdown in detail.
Langkah yang sama dalam satu prompt untuk asisten coding (Claude Code, Cursor, Copilot…). Tempel ke asisten di folder kosong; ia menjalankan tiap langkah dan berhenti di error pertama. Prompt-nya sengaja berbahasa Inggris.
Build the Samveys tutorial app "notes" with me: a small notes API with a database table, a validated route,
a test and a Docker image. Run each command yourself, in order, and stop and show me the error if a step fails.
@samveys/* comes from https://registry.sadata.dev/api/packages/sadata/npm/ (readable without a token), never from the public npm registry.
1. Create the project: `svs new notes --kit=api` (or `npx @samveys/console new notes --kit=api` if svs is not installed),
then `cd notes && pnpm install && pnpm test`. From here on run the CLI as `pnpm svs <command>`.
2. `pnpm svs make:model Note --migration`, then give app/models/note.ts these columns with Model.define('notes', …) from @samveys/orm:
id col.increments(), title col.string(120), body col.text().nullable(), createdAt col.timestamp().autoCreate().
Then `pnpm svs make:migration create_notes --diff` and `pnpm svs migrate`. Do not hand-write the migration.
3. In app/routes.ts (defineRoutes from @samveys/http) keep GET /ping → 'pong' and add:
- GET /notes: the 50 newest notes as JSON.
- POST /notes with body schema `schema.object({ title: schema.string().min(1).max(120), body: schema.string().optional() })`,
creating the note and answering response.created(note.toJSON()). Let the schema reject bad bodies (422); add no manual checks.
4. Add a test in tests/ with createTestApp() from @samveys/testing and an in-memory SQLite database:
POST a valid note → 201; an unknown field (isAdmin) must not reach the model; POST { title: '' } → 422; GET /notes returns 1 note.
Run `pnpm test`.
5. `pnpm svs check --deploy` and `pnpm svs route:list`. Tell me what check --deploy reports.
6. `pnpm svs build --docker` and `docker build -t notes .`. Do not put secrets or .env files into the image;
configuration comes from environment variables (APP_ENV, APP_KEY, DATABASE_URL).
When you are done, list each step with what you changed or ran and its result.