File-Based Routing
bro.js uses a powerful file-system-based router. Any .js file inside the routes/ directory automatically becomes an API endpoint.
Basic Example
Create routes/hello.get.js:
import { defineRoute } from 'bro-framework';
export default defineRoute({
handler: async () => {
return { message: 'Hello World!' };
}
});This instantly maps to GET /hello.
Dynamic Routes
Need path parameters? Just use brackets!
Create routes/users/[id].get.ts:
import { defineRoute, z } from 'bro-framework';
export default defineRoute({
params: z.object({ id: z.string() }),
handler: async ({ params }) => {
return { userId: params.id };
}
});This maps to GET /users/:id and strictly validates that id is a string!
Bracket Naming Rules
Dynamic bracket parameters MUST be valid JavaScript identifiers (matching ^[a-zA-Z_$][a-zA-Z0-9_$]*$). Malformed names (like [user-id]) or invalid HTTP methods (like .psot.ts) will fail immediately at boot.
Nested Dynamic Routes
You can nest dynamic routes endlessly.
Create routes/orgs/[orgId]/projects/[projectId].get.ts:
import { defineRoute, z } from 'bro-framework';
export default defineRoute({
params: z.object({
orgId: z.string(),
projectId: z.string()
}),
handler: async ({ params }) => {
return { orgId: params.orgId, projectId: params.projectId };
}
});This maps to GET /orgs/:orgId/projects/:projectId.
SDK Collision Guard
When defining routes, you cannot create overlapping static and dynamic segments at the same tree depth in the same directory.
For example, having both routes/users/me.get.js (static) and routes/users/[id].get.js (dynamic) will throw an SDK Collision error to protect the auto-generated client logic. Keep your static and dynamic endpoints neatly separated!
OpenAPI Success Schemas
If you want your successful responses to be strictly typed in the generated OpenAPI documentation, you can export a response Zod schema. This is strictly opt-in. If you don't provide one, your handler's return value will still be seamlessly served as a 200 OK JSON response, maintaining our zero-boilerplate philosophy.
import { defineRoute, z } from 'bro-framework';
export default defineRoute({
response: z.object({
userId: z.string()
}),
handler: async () => {
return { userId: "123" };
}
});Zero-Config Route Caching
If you've connected Redis via bro.config.js (using redisUrl), you can trivially cache any endpoint by adding a cache: <seconds> property. The framework will instantly intercept identical requests and serve the response straight from Redis, completely bypassing your handler!
export default defineRoute({
cache: 60, // Cache this response in Redis for 60 seconds
handler: async ({ db }) => {
// This heavy query will only run once per minute!
return await db.collection('heavyStats').aggregate(...);
}
});