Routing
Signatureβ
All routes in CrumbJS follow the same signature:
// Route signature
app[method](path, handler, options?);
app.get("/path", handler, routeConfig);
app.post("/path", handler, routeConfig);
app.put("/path", handler, routeConfig);
app.patch("/path", handler, routeConfig);
app.delete("/path", handler, routeConfig);
app.options("/path", handler, routeConfig);
app.head("/path", handler, routeConfig);
app.proxy(method, localPath, dest, routeConfig);
app.proxyAll(localPath, dest, use);
app.static(path, content, type)
Wildcards and paramsβ
// Basic param usage (inferred as string)
app.get("/user/:id", ({ params }) => {
return `User id is: ${params.id}`; // is type-safe too without defining schema
});
// Defining validation rules for the path params
app.get("/user/:id", ({ params }) => {
return `User id is: ${params.id}`;
}, {
params: z.object({ // must be a Zod Object
id: z.string().min(36), // must define rules to all index without ':'
})
});
// Some advanced things you can do with Path Params + Zod + Drizzle
app.get(
"/user/:user/posts",
async ({ params }) => {
return params.user.name; // return user name from "db"
},
{
// If `params` is defined, you must describe ALL path params
params: z.object({
user: z.string().transform(async (val) => {
const user = await db.query.users.findFirst({
where: eq(users.id, val)
});
if (!user) throw new NotFound();
return user;
}),
}),
}
);
// Wildcard (match everything after /user/)
app.get("/user/*", ({ params }) => {
return `Matched path: ${params["*"]}`;
});
Proxyβ
CrumbJS provides built-in support for proxying requests to other services.
This is useful for API gateways, authentication forwarding, or integrating multiple microservices.
πΉ Single Proxyβ
Use app.proxy()
to forward a specific method and path to a target URL.
These routes may be fully documented with OpenAPI, including validation and middlewares.
app.proxy(
'POST',
'/auth',
'https://auth-ms.example.com/v1/auth',
{ body: authRequestDto, use: [guestMiddleware] }
);
πΉ Proxy All Methodsβ
Use app.proxyAll()
to forward all HTTP methods under a path prefix.
This is useful when you donβt need per-route documentation.
β οΈ Note: These routes cannot be pre-validated or documented with OpenAPI, but you can still attach middlewares.
app.proxyAll('/v1', 'https://microservice-a.example.com/v1', [authMiddleware]);
// Example: '/v1/any/path' β https://microservice-a.example.com/v1/any/path
app.proxyAll('/v2', 'https://microservice-a.example.com/v2', [authMiddleware]);
// Example: '/v2/any/path' β https://microservice-a.example.com/v2/any/path
Key Pointsβ
proxy()
β Single method + path, may be validated with zod, documented with OpenAPI and use middleware/s.proxyAll()
β All methods + prefix forwarding, no validation and not documentable with OpenAPI, but supports middleware/s.
Static Routesβ
Static routes allow you to serve files or strings directly from memory at application startup.
This is useful for serving assets (PDFs, HTML snippets, images) without setting up a full static file server.
πΉ Content Loadingβ
- Files (
Blob
): The file is read into memory on startup.- If no
Content-Type
is provided, it will be inferred from the Blob.
- If no
- Strings: Sent as-is.
- If no
Content-Type
is provided, it defaults toapplication/octet-stream
.
- If no
πΉ Referenceβ
For more details on Bunβs static responses, check the official docs:
π Bun.serve - static-responses
πΉ Usageβ
/**
* Signature
* static(path: string, content: string | Blob, type?: ContentType)
*/
// Serve a file, type inferred from Blob
app.static('/document.pdf', Bun.file('/path/to/file.pdf'));
// Serve a file but force download (octet-stream)
app.static('/document.pdf', Bun.file('/path/to/file.pdf'), 'application/octet-stream');
// Serve inline HTML
app.static('/content', '<html><body>Hello</body></html>', 'text/html');
- All static content is eagerly loaded into memory at application startup.
- Best suited for small to medium FILES or predefined responses (e.g. logos, HTML snippets, config files).
- β οΈ Not recommended for very large FILES (e.g. files >500 MB), since this approach keeps them entirely in memory.
For such cases, consider create a streaming route or use a dedicated static file server instead.
Handlersβ
A route handler in CrumbJS receives a single argument: the Context
object.
Context
is a rich object that contains everything you need to work with the request and response.
We will dive deeper into Context
in the next section.
The return value of a handler must be one of the following:
- an object (automatically serialized to JSON),
- null, or
- a Response instance.
app.get("/path-to-route", async (ctx) => {
return { message: "Hello World" }; // object, null, or Response
});