Build an API service
You will create an API service with file-based routing, then add schema validation and typed request handling.
Companion sample:
samples/ts/02-rest-api— a runnable CRUD API with validation and OpenAPI. Runbunx putnami serve @sample/ts-rest-apifrom the workspace root.
Steps
1) Create the workspace and API project
putnami init --workspace my-workspace
cd my-workspace
putnami projects create api --template typescript-serverThe typescript-server template scaffolds an API project with @putnami/application, file-based routing, and example GET/POST endpoints.
2) Run it
putnami serve apiOpen http://localhost:3000. You see the default JSON response from the GET endpoint.
3) Explore the project structure
The scaffolded project lives in packages/api/:
packages/api/
├── src/
│ ├── main.ts # Application entrypoint
│ ├── serve.ts # Server bootstrap
│ └── api/
│ ├── get.ts # GET / endpoint
│ └── post.ts # POST / endpoint
└── test/
└── api.test.ts4) Add a health check route
Create packages/api/src/api/health/get.ts:
import { endpoint } from '@putnami/application';
export default endpoint(() => {
return { ok: true };
});Navigate to http://localhost:3000/health.
5) Add a validated route
Create packages/api/src/api/items/post.ts:
import { endpoint, MinLength, Optional, ArrayOf } from '@putnami/application';
export default endpoint()
.body({
name: MinLength(1),
description: Optional(String),
tags: Optional(ArrayOf(String)),
})
.handle(async (ctx) => {
const { name, description, tags } = await ctx.body();
return {
item: {
id: crypto.randomUUID(),
name,
description: description ?? '',
tags: tags ?? [],
},
};
});Sending an invalid body returns a 400 automatically:
POST /items
Content-Type: application/json
{ "name": "" }
-> 400 { "message": "body.name must have length >= 1", ... }6) Add a route with path parameter validation
Create packages/api/src/api/items/[id]/get.ts:
import { endpoint, Uuid } from '@putnami/application';
export default endpoint()
.params({ id: Uuid })
.handle((ctx) => {
// ctx.params.id is typed as string and validated as UUID
return { item: { id: ctx.params.id } };
});Result
You have a running API service with file-based routing and built-in schema validation. Invalid requests are rejected before your handler runs with structured error messages.
Next steps
- See the API reference for all schema types (
Uuid,Email,Int,Min,Max,Pattern, ...) and builder methods - Add persistence for a database-backed API
- Add authentication to protect routes