CRUD API
Executable 2.x Reference
The release contract for this page is the checked-in
examples/crud-api
project. It is a TypeScript Todo API backed by an isolated MongoDB database and
the raw MonSQLize instance exposed only as app.db.
Start a reachable MongoDB instance. From the repository root, run npm ci
and npm run build first. The database name below is illustrative; choose
your own isolated test database. This app defaults to port 3100, unlike the
in-memory teaching variant below, which uses port 3000.
The model declares collection: "todos", so the service uses the exact raw
registry key app.db.model("todos"). The application explicitly keeps global
rate limiting off and enables OpenAPI/Vext Docs.
Its real endpoints are GET /, GET /todos, POST /todos,
GET /todos/:id, PATCH /todos/:id, and DELETE /todos/:id. A required
path id uses string:1-! and appears as required: true in OpenAPI.
After a parameterized route matches, invalid parameters return HTTP 400;
body and query validation failures return HTTP 422. An unmatched path is a
different case: GET /todos enters the list route and cannot test a
"missing id returns 400" claim.
npm test currently checks the example's source contract; it does not run
Mongo CRUD. Make real requests to verify the database flow. A mock database
does not prove a real connection. With the app running, use another
PowerShell terminal:
Expect 201 on create, 200 on read and update, and 200 with deleted: true
on delete. Reading the same ID again should return 404; an empty title
should return 422. /openapi.json and /docs should include Todo routes.
Stop the example when done and clean up only your test records.
Extended In-memory/Auth Tutorial
The walkthrough below is a separate teaching variant with users, in-memory
storage, and an auth middleware. It is useful for illustrating more APIs, but
it is not the executable examples/crud-api fixture or its endpoint contract.
Project structure
1. Initialize project
Keep the API template's package.json and tsconfig. Add "test": "vitest run" and "typecheck": "tsc --noEmit" scripts. Write the files below to
their commented paths. This variant uses a local Map: each process has its
own state, restarts reset it, and it has no cross-process uniqueness or
persistence guarantee.
2. Configuration
auth() only parses the credential and fills req.auth. Routes opt into protection with RouteOptions.auth; OpenAPI security is generated from that route option first, with legacy middleware-name inference kept only for older examples.
3. Authentication middleware
Statically projectable protected routes
Route options are consumed by build indexing, Doctor, and OpenAPI generation without executing route modules. Keep the final authentication shape in the inline options object, or in a same-file const passed directly to one route call. A helper call around the options object is rejected by the finite static grammar, so the protected routes below inline middlewares and auth explicitly.
4. Service layer
VextJS loads services from the conventional src/services/ directory. Each file must default-export a class or constructor that can be instantiated with new; a plain object default export fails loading. The instantiated service is injected into app.services. The file name is the service name: user.ts → app.services.user. The testing helper's mockServices accepts object substitutes under a separate contract; see Testing API.
The constructor of the service receives the app: VextApp parameter and can access app.logger, app.config, app.throw and other framework capabilities.
5. Routing
Root route (health check)
User routing (full CRUD)
6. Startup entry
Use the template's vext dev/build/start scripts. The CLI manages startup,
so no additional src/index.ts is needed. Run pnpm typecheck and
pnpm build before pnpm start. See Services for service
type generation and manual extension.
7. Test
8. Run
createTestApp does not read the project's default.ts, so the test above
passes its middleware whitelist explicitly. These source routes are imported
directly by Node; use Node.js 22.18+ or equivalent default type stripping.
Installing Vitest alone does not let Node 20 import TypeScript routes.
See Testing for loaders and compiled-output alternatives.
Each test app creates its own UserService and seed data; it does not write to
the repository Todo example's MongoDB database. Also test missing required
name/email, omitted or fractional pagination, invalid tokens, and unchanged
fields when an update omits them.
Development mode
After startup you can:
- Visit
http://localhost:3000/to view the health check - Visit
http://localhost:3000/docsto view the automatically generated Vext Docs API documentation - Use
curlto test each interface
Run the test
9. Interface testing
10. Summary of key concepts
Request-response process
Error handling process
Design patterns
Next step
- 📖 permission-core Auth — Connect a fine-grained authorization core to Vext Auth
- 📖 Testing — Learn more about advanced usage of VextJS testing tools
- 📖 OpenAPI Documentation — Learn more about OpenAPI’s auto-generated configuration options