Skip to content

Procedures

Procedures are the core building block of VeloxTS APIs. They define type-safe endpoints with input validation, output schemas, and handlers.

import { procedures, procedure } from '@veloxts/velox';
import { z } from '@veloxts/velox';
export const userProcedures = procedures('users', {
getUser: procedure()
.input(z.object({ id: z.string() }))
.output(UserSchema)
.query(async ({ input, ctx }) => {
return ctx.db.user.findUniqueOrThrow({ where: { id: input.id } });
}),
});

Define the input validation schema:

.input(z.object({
id: z.string().uuid(),
includeDeleted: z.boolean().optional(),
}))

Define the output schema (optional but recommended):

.output(z.object({
id: z.string(),
name: z.string(),
email: z.string(),
}))

Define the handler function:

  • .query() - For read operations (GET)
  • .mutation() - For write operations (POST, PUT, DELETE)
.query(async ({ input, ctx }) => {
// input is typed based on .input() schema
// ctx contains request context
return ctx.db.user.findUniqueOrThrow({ where: { id: input.id } });
})

Add authorization guards:

.guard(authenticated)
.guard(hasRole('admin'))

Add middleware:

.use(rateLimitMiddleware)
.use(loggingMiddleware)

Override REST endpoint generation:

.rest({ method: 'POST', path: '/auth/login' })
.rest({ enabled: false }) // tRPC-only

See REST Overrides for full options.

The handler receives { input, ctx }:

.query(async ({ input, ctx }) => {
// input: Validated input data
// ctx.db: Prisma client
// ctx.request: Fastify request
// ctx.reply: Fastify reply
// ctx.user: Authenticated user (if using auth)
})

Group related procedures with procedures():

export const postProcedures = procedures('posts', {
listPosts: procedure()...,
getPost: procedure()...,
createPost: procedure()...,
updatePost: procedure()...,
deletePost: procedure()...,
});

The first argument ('posts') becomes:

  • The REST resource name: /api/posts
  • The tRPC namespace: trpc.posts.listPosts

Types flow automatically:

// Backend
const createUser = procedure()
.input(z.object({ name: z.string(), email: z.string().email() }))
.output(z.object({ id: z.string(), name: z.string() }))
.mutation(...)
// Frontend (via tRPC or client)
// Input is typed: { name: string; email: string }
// Output is typed: { id: string; name: string }

Procedures are automatically discovered from src/procedures/:

src/index.ts
import { discoverProcedures } from '@veloxts/router';
const collections = await discoverProcedures('./src/procedures');
collections.forEach(c => app.procedures(c));