File-Based Routing: A Proven Pattern
Next.js proved that file-based routing eliminates boilerplate. Instead of manually configuring routes, you create files in a directory structure, and the framework discovers them automatically.
VentureKit brings this pattern to serverless API development. Your directory structure is your API:
src/routes/
├── health/
│ └── get.ts → GET /health
├── projects/
│ ├── get.ts → GET /projects
│ ├── post.ts → POST /projects
│ └── [id]/
│ ├── get.ts → GET /projects/:id
│ ├── put.ts → PUT /projects/:id
│ └── delete.ts → DELETE /projects/:id
└── webhooks/
└── stripe/
└── post.ts → POST /webhooks/stripeNo router configuration. No route registration. No middleware chains to set up. Drop a file, get an endpoint.
How It Works
Each route file exports a handler using VentureKit's unified handler pattern. Zod validation is built into the handler config — you pass your schema as body, query, or params and VentureKit validates automatically before your handler runs:
// src/routes/projects/post.ts
import { handler } from '@venturekit/runtime';
import { z } from 'zod';
const CreateProject = z.object({
name: z.string().min(1),
description: z.string().optional(),
});
export const main = handler(
async (body, ctx, log) => {
const project = await ctx.db.insert('projects', body);
return project;
},
{
scopes: ['projects.write'],
body: CreateProject,
},
);The handler receives the already-validated body as its first argument. If validation fails, VentureKit returns a structured 422 error with field-level details — your handler never runs.
VentureKit's runtime handles everything around your business logic:
scopes is specified)body, query, and params validated before the handler runsPublic vs. Authenticated Endpoints
The handler's second argument controls access:
// Public endpoint — no auth required
export const main = handler(fn);
// Authenticated — requires valid JWT with api.read scope
export const main = handler(fn, { scopes: ['api.read'] });
// Admin only — requires admin scope
export const main = handler(fn, { scopes: ['admin'] });No auth middleware to configure. No route-level guards to maintain. The pattern is declarative and type-safe.
Dynamic Parameters
Square brackets in directory names create dynamic path parameters:
src/routes/projects/[id]/get.ts → GET /projects/:id
src/routes/orgs/[orgId]/users/get.ts → GET /orgs/:orgId/usersParameters are available on ctx.params with full TypeScript inference.
Local Development
During vk dev, VentureKit scans your routes directory and creates an Express-compatible HTTP server that mimics Lambda behavior:
When you deploy with vk deploy, each route becomes an individual Lambda function behind API Gateway — no code changes required.
Why File-Based Routing for APIs?
Discoverability
New developers can understand your entire API surface by looking at the file tree. No need to trace through router configurations or middleware chains.
Colocation
Related code lives together. A route's handler, validation schema, and tests all live in the same directory.
Automatic OpenAPI
VentureKit generates an OpenAPI specification from your route structure, handler types, and Zod schemas. Your API documentation stays in sync with your code automatically.
Zero Configuration
No app.get('/projects/:id', ...) registrations. No Express router imports. No middleware ordering bugs. The file system is the single source of truth.