@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
323 lines (272 loc) • 9.74 kB
Markdown
# Custom API routes
By default, Mastra automatically exposes registered agents and workflows via its server. For additional behavior you can define your own HTTP routes.
Routes are provided with a helper `registerApiRoute()` from `@mastra/core/server`. Routes can live in the same file as the `Mastra` instance but separating them helps keep configuration concise.
```typescript
import { Mastra } from '@mastra/core'
import { registerApiRoute } from '@mastra/core/server'
export const mastra = new Mastra({
server: {
apiRoutes: [
registerApiRoute('/my-custom-route', {
method: 'GET',
handler: async c => {
const mastra = c.get('mastra')
const agent = await mastra.getAgent('my-agent')
return c.json({ message: 'Custom route' })
},
}),
],
},
})
```
Once registered, a custom route will be accessible from the root of the server. For example:
```bash
curl http://localhost:4111/my-custom-route
```
Each route's handler receives the Hono `Context`. Within the handler you can access the `Mastra` instance to fetch or call agents and workflows.
## Middleware
To add route-specific middleware pass a `middleware` array when calling `registerApiRoute()`.
```typescript
import { Mastra } from '@mastra/core'
import { registerApiRoute } from '@mastra/core/server'
export const mastra = new Mastra({
server: {
apiRoutes: [
registerApiRoute('/my-custom-route', {
method: 'GET',
middleware: [
async (c, next) => {
console.log(`${c.req.method} ${c.req.url}`)
await next()
},
],
handler: async c => {
return c.json({ message: 'Custom route with middleware' })
},
}),
],
},
})
```
## OpenAPI documentation
Custom routes can include OpenAPI metadata to appear in the Swagger UI alongside Mastra server routes. You can access the OpenAPI spec at `/api/openapi.json`, where both custom routes and built-in routes are listed. Pass an `openapi` option with standard OpenAPI operation fields.
```typescript
import { Mastra } from '@mastra/core'
import { registerApiRoute } from '@mastra/core/server'
import { z } from 'zod'
export const mastra = new Mastra({
server: {
apiRoutes: [
registerApiRoute('/items/:itemId', {
method: 'GET',
openapi: {
summary: 'Get item by ID',
description: 'Retrieves a single item by its unique identifier',
tags: ['Items'],
parameters: [
{
name: 'itemId',
in: 'path',
required: true,
description: 'The item ID',
schema: { type: 'string' },
},
],
responses: {
200: {
description: 'Item found',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
},
},
},
},
},
404: {
description: 'Item not found',
},
},
},
handler: async c => {
const itemId = c.req.param('itemId')
return c.json({ id: itemId, name: 'Example Item' })
},
}),
],
},
})
```
### Using Zod Schemas
Zod schemas in the `openapi` configuration are converted to JSON Schema when the OpenAPI document is generated:
```typescript
import { Mastra } from '@mastra/core'
import { registerApiRoute } from '@mastra/core/server'
import { z } from 'zod'
const ItemSchema = z.object({
id: z.string(),
name: z.string(),
price: z.number(),
})
const CreateItemSchema = z.object({
name: z.string().min(1),
price: z.number().positive(),
})
export const mastra = new Mastra({
server: {
apiRoutes: [
registerApiRoute('/items', {
method: 'POST',
openapi: {
summary: 'Create a new item',
tags: ['Items'],
requestBody: {
required: true,
content: {
'application/json': {
schema: CreateItemSchema,
},
},
},
responses: {
201: {
description: 'Item created',
content: {
'application/json': {
schema: ItemSchema,
},
},
},
},
},
handler: async c => {
const body = await c.req.json()
return c.json({ id: 'new-id', ...body }, 201)
},
}),
],
},
})
```
### Viewing in Swagger UI
When running in development mode (`mastra dev`) or with `swaggerUI: true` in build options, your custom routes appear in the Swagger UI at `/swagger-ui`.
```typescript
export const mastra = new Mastra({
server: {
build: {
swaggerUI: true, // Enable in production builds
},
apiRoutes: [
// Your routes...
],
},
})
```
## Authentication
When authentication is configured on your Mastra server, custom API routes require authentication by default. To make a route publicly accessible, set `requiresAuth: false`:
```typescript
import { Mastra } from '@mastra/core'
import { registerApiRoute } from '@mastra/core/server'
import { MastraJwtAuth } from '@mastra/auth'
export const mastra = new Mastra({
server: {
auth: new MastraJwtAuth({
secret: process.env.MASTRA_JWT_SECRET,
}),
apiRoutes: [
// Protected route (default behavior)
registerApiRoute('/protected-data', {
method: 'GET',
handler: async c => {
// Access authenticated user from request context
const user = c.get('requestContext').get('user')
return c.json({ message: 'Authenticated user', user })
},
}),
// Public route (no authentication required)
registerApiRoute('/webhooks/github', {
method: 'POST',
requiresAuth: false, // Explicitly opt out of authentication
handler: async c => {
const payload = await c.req.json()
// Process webhook without authentication
return c.json({ received: true })
},
}),
],
},
})
```
### Authentication behavior
- **No auth configured**: All routes (built-in and custom) are public
- **Auth configured**:
- Mastra-provided routes (`/api/agents/*`, `/api/workflows/*`, etc.) require authentication
- Custom routes require authentication by default
- Custom routes can opt out with `requiresAuth: false`
### Accessing user information
When a request is authenticated, the user object is available in the request context:
```typescript
registerApiRoute('/user-profile', {
method: 'GET',
handler: async c => {
const requestContext = c.get('requestContext')
const user = requestContext.get('user')
return c.json({ user })
},
})
```
For more information about authentication providers, see the [Auth documentation](https://mastra.ai/docs/server/auth).
## Continue generation after client disconnect
Built-in streaming helpers such as [`chatRoute()`](https://mastra.ai/reference/ai-sdk/chat-route) forward the incoming request's `AbortSignal` to `agent.stream()`. That's the right default when a browser disconnect should cancel the model call.
If you want the server to keep generating and persist the final response even after the client disconnects, build a custom route around the underlying `MastraModelOutput`. Start the agent stream without forwarding `c.req.raw.signal`, then call `consumeStream()` in the background so generation continues server-side.
```typescript
import {
createUIMessageStream,
createUIMessageStreamResponse,
InferUIMessageChunk,
UIMessage,
} from 'ai'
import { toAISdkStream } from '@mastra/ai-sdk'
import { Mastra } from '@mastra/core'
import { registerApiRoute } from '@mastra/core/server'
export const mastra = new Mastra({
server: {
apiRoutes: [
registerApiRoute('/chat/persist/:agentId', {
method: 'POST',
handler: async c => {
const { messages, memory } = await c.req.json()
const mastra = c.get('mastra')
const agent = mastra.getAgent(c.req.param('agentId'))
const stream = await agent.stream(messages, {
memory,
// Do not pass c.req.raw.signal if this route should keep running
// after the client disconnects.
})
void stream.consumeStream().catch(error => {
mastra.getLogger()?.error('Background stream consumption failed', { error })
})
const uiStream = createUIMessageStream({
originalMessages: messages,
execute: async ({ writer }) => {
for await (const part of toAISdkStream(stream, { from: 'agent' })) {
writer.write(part as InferUIMessageChunk<UIMessage>)
}
},
})
return createUIMessageStreamResponse({ stream: uiStream })
},
}),
],
},
})
```
> **Note:** Use this pattern only when you intentionally want work to continue after the HTTP client is gone. If you want disconnects to cancel generation, keep using `chatRoute()` or forward the request `AbortSignal` yourself.
## Related
- [registerApiRoute() Reference](https://mastra.ai/reference/server/register-api-route): Full API reference
- [Server Middleware](https://mastra.ai/docs/server/middleware): Global middleware configuration
- [Mastra Server](https://mastra.ai/docs/server/mastra-server): Server configuration options