Skip to content

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.

  • External clients that aren’t TypeScript
  • Mobile apps (iOS, Android)
  • Third-party integrations
  • Public APIs
  • OpenAPI/Swagger documentation needed
Terminal window
npx create-velox-app my-api
# or with auth
npx create-velox-app my-api --auth

Define procedures with naming conventions, and Velox TS generates REST endpoints:

src/procedures/posts.ts
import { procedures, procedure, resourceSchema, resource, resourceCollection } from '@veloxts/velox';
import { z } from '@veloxts/velox';
// Define resource schema with field visibility
const 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 };
}),
});

See REST Conventions for the complete reference.

PrefixMethodPath Pattern
list*GET/api/{resource}
get*GET/api/{resource}/:id
create*POST/api/{resource}
update*PUT/api/{resource}/:id
delete*DELETE/api/{resource}/:id

Override conventions with .rest():

sendPasswordReset: procedure()
.input(z.object({ email: z.string().email() }))
.mutation(handler)
.rest({
method: 'POST',
path: '/auth/password-reset',
}),

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.