REST API Architecture
The REST architecture generates standard HTTP endpoints from your procedure definitions using naming conventions. Your API gets predictable URLs, proper HTTP methods, and automatic OpenAPI documentation — all without manually defining routes. Choose this when your consumers aren’t TypeScript or need a conventional REST interface.
When to Choose REST
Section titled “When to Choose REST”- External clients that aren’t TypeScript
- Mobile apps (iOS, Android)
- Third-party integrations
- Public APIs
- OpenAPI/Swagger documentation needed
Project Setup
Section titled “Project Setup”npx create-velox-app my-api# or with authnpx create-velox-app my-api --authHow It Works
Section titled “How It Works”Define procedures with naming conventions, and Velox TS generates REST endpoints:
import { procedures, procedure, resourceSchema, resource, resourceCollection } from '@veloxts/velox';import { z } from '@veloxts/velox';
// Define resource schema with field visibilityconst PostSchema = resourceSchema() .public('id', z.string()) .public('title', z.string()) .public('excerpt', z.string()) .authenticated('content', z.string()) .authenticated('authorId', z.string()) .admin('createdAt', z.date()) .admin('updatedAt', z.date()) .build();
export const postProcedures = procedures('posts', { // GET /api/posts — public fields only listPosts: procedure() .query(async ({ ctx }) => { const posts = await ctx.db.post.findMany(); return resourceCollection(posts, PostSchema.public); }),
// GET /api/posts/:id — public fields only getPost: procedure() .input(z.object({ id: z.string() })) .query(async ({ input, ctx }) => { const post = await ctx.db.post.findUniqueOrThrow({ where: { id: input.id } }); return resource(post, PostSchema.public); }),
// POST /api/posts — returns authenticated-level fields createPost: procedure() .input(CreatePostSchema) .mutation(async ({ input, ctx }) => { const post = await ctx.db.post.create({ data: input }); return resource(post, PostSchema.authenticated); }),
// PUT /api/posts/:id — returns authenticated-level fields updatePost: procedure() .input(z.object({ id: z.string(), data: UpdatePostSchema })) .mutation(async ({ input, ctx }) => { const post = await ctx.db.post.update({ where: { id: input.id }, data: input.data, }); return resource(post, PostSchema.authenticated); }),
// DELETE /api/posts/:id deletePost: procedure() .input(z.object({ id: z.string() })) .mutation(async ({ input, ctx }) => { await ctx.db.post.delete({ where: { id: input.id } }); return { success: true }; }),});REST Naming Conventions
Section titled “REST Naming Conventions”See REST Conventions for the complete reference.
| Prefix | Method | Path Pattern |
|---|---|---|
list* | GET | /api/{resource} |
get* | GET | /api/{resource}/:id |
create* | POST | /api/{resource} |
update* | PUT | /api/{resource}/:id |
delete* | DELETE | /api/{resource}/:id |
Custom Routes
Section titled “Custom Routes”Override conventions with .rest():
sendPasswordReset: procedure() .input(z.object({ email: z.string().email() })) .mutation(handler) .rest({ method: 'POST', path: '/auth/password-reset', }),OpenAPI Documentation
Section titled “OpenAPI Documentation”Enable automatic Swagger UI:
import { swaggerPlugin } from '@veloxts/router';
await app.server.register(swaggerPlugin, { routePrefix: '/api/docs', collections: [userProcedures, postProcedures], openapi: { info: { title: 'My API', version: '1.0.0' }, },});Visit http://localhost:3030/api/docs for interactive API documentation.
Related Content
Section titled “Related Content”- REST Conventions - All naming patterns
- OpenAPI - API documentation
- Authentication - Secure your API