Custom API routes

Trust: ★★★☆☆ (0.90) · 0 validations · developer_reference

Published: 2026-05-10 · Source: crawler_authoritative

Tình huống

Mastra documentation for developers defining custom HTTP routes in the Mastra server, covering route registration, middleware, OpenAPI documentation, authentication, and client disconnect streaming behavior.

Insight

Mastra provides the registerApiRoute() helper from @mastra/core/server for defining custom HTTP routes. Routes are configured in the Mastra constructor under server.apiRoutes. Each route handler receives the Hono Context, which provides access to the Mastra instance via c.get('mastra') for fetching agents and workflows, as well as request context. Routes are accessible from the server root (e.g., http://localhost:4111/my-custom-route). Route-specific middleware can be added via the middleware array property. Custom routes support OpenAPI metadata via the openapi option with standard fields including summary, description, tags, parameters, requestBody, and responses. Zod schemas in openapi configuration are automatically converted to JSON Schema. The OpenAPI spec is available at /api/openapi.json and routes appear in Swagger UI at /swagger-ui when swaggerUI: true is set in build options. Authentication via MastraJwtAuth makes custom routes protected by default; use requiresAuth: false to make a route public. When authenticated, the user object is available via c.get('requestContext').get('user'). For streaming that persists after client disconnect, use agent.stream() without forwarding c.req.raw.signal, then call consumeStream() in the background with utilities from ai and @mastra/ai-sdk.

Hành động

Import registerApiRoute from @mastra/core/server and configure routes in the Mastra constructor’s server.apiRoutes array. Define route handlers with method (GET, POST, etc.) and async handler function receiving Hono Context. Access the Mastra instance with c.get('mastra') and agent via mastra.getAgent('agent-name'). Return responses using c.json(). Add middleware via the middleware array of async functions. For OpenAPI docs, include an openapi object with summary, description, tags, parameters, requestBody, and responses fields—Zod schemas are automatically converted. Enable Swagger UI in production with server.build.swaggerUI: true. For authentication, configure MastraJwtAuth and use requiresAuth: false on routes that should be public. Access authenticated user via c.get('requestContext').get('user'). For background streaming after disconnect, use agent.stream() without passing the request AbortSignal, call stream.consumeStream() to continue generation, and transform the stream with toAISdkStream and createUIMessageStream.

Kết quả

Custom routes are accessible from the server root. Middleware runs before the handler for each matched route. OpenAPI metadata appears in Swagger UI and the spec at /api/openapi.json. Auth-protected routes return authentication errors unless requiresAuth: false is set. Background streaming routes continue generating and return the final response even after client disconnection.


Nội dung gốc (Original)

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.

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:

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().

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.

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:

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.

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:

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:

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.

Continue generation after client disconnect

Built-in streaming helpers such as chatRoute() 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.

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.

Liên kết

Xem thêm: