On this page
API Routes
Analog supports defining API routes that can be used to serve data to the application.
Defining an API Route
API routes are defined in the src/server/routes/api folder. API routes are also filesystem based, and are exposed under the default /api prefix.
import { defineEventHandler } from 'h3';
export default defineEventHandler(() => ({ message: 'Hello World' }));
Validating an API Route with a Schema
defineApiRoute adds Standard Schema validation to an API handler. For example,
with Zod 3.24+:
// src/server/routes/api/users.post.ts
import { defineApiRoute } from '@analogjs/router/server/actions';
import { z } from 'zod';
export default defineApiRoute({
body: z.object({ name: z.string().min(1) }),
handler: ({ body }) => ({ name: body.name }),
});
params,query, andbodyvalidate their respective request values and infer the corresponding handler argument types. Body validation runs for methods other than GET and HEAD.inputvalidates query parameters for GET/HEAD, or the body for other methods, and provides its result asdata. If separate schemas are also configured, they are validated too. Withoutinput,datauses the validated query for GET/HEAD, or the validated body (falling back to query) for other methods.- Repeated query and form fields remain arrays. JSON, URL-encoded forms, and
multipart forms are supported. Empty or unparseable bodies fall back to
{}. - Invalid input returns HTTP 422 with a Standard Schema issues array and the
X-Analog-Errorsheader, without calling the handler. - Plain return values become JSON responses. A returned
Response, including one fromjson,redirect, orfail, passes through unchanged. - An optional
outputschema checks plain return values in development and tests. Failures produce a warning; they do not change the response. Output validation does not run in production.
Schemas may validate asynchronously. The handler also receives the original h3
event for cookies, headers, and other request operations.
Inferring types on the client
The route returned by defineApiRoute carries its validated query, body, and
result types. Export it as a named route as well as the default export to share
those types with the client:
// src/server/routes/api/v1/todos.get.ts
import { defineApiRoute } from '@analogjs/router/server/actions';
import * as v from 'valibot';
export const route = defineApiRoute({
query: v.object({ scope: v.optional(v.string(), 'default') }),
handler: ({ query }) => [{ id: 1, title: `${query.scope} todo` }],
});
export default route;
Use InferRouteQuery, InferRouteBody, and InferRouteResult from
@analogjs/router/server/actions with typeof route, or pass the route to the
TanStack Query integration.
Defining XML Content
To create an RSS feed for your site, set the content-type to be text/xml and Analog serves up the correct content type for the route.
//server/routes/api/rss.xml.ts
import { defineEventHandler, setResponseHeader } from 'h3';
export default defineEventHandler((event) => {
const feedString = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
</rss>
`;
setResponseHeader(event, 'content-type', 'text/xml');
return feedString;
});
Note: For SSG content, set Analog to prerender an API route to make it available as prerendered content:
// vite.config.ts
...
prerender: {
routes: async () => {
return [
...
'/api/rss.xml',
...
.
];
},
sitemap: {
host: 'https://analog-blog.netlify.app',
},
},
The XML is available as a static XML document at /dist/analog/public/api/rss.xml
Dynamic API Routes
Dynamic API routes are defined by using the filename as the route path enclosed in square brackets. Parameters can be accessed via event.context.params.
// /server/routes/api/v1/hello/[name].ts
import { defineEventHandler } from 'h3';
export default defineEventHandler(
(event) => `Hello ${event.context.params?.['name']}!`,
);
Another way to access route parameters is by reading them from event.context.params inside the handler.
// /server/routes/api/v1/hello/[name].ts
import { defineEventHandler } from 'h3';
export default defineEventHandler((event) => {
const name = event.context.params?.['name'] ?? 'friend';
return `Hello, ${name}!`;
});
Specific HTTP request method
File names can be suffixed with .get, .post, .put, .delete, etc. to match the specific HTTP request method.
GET
// /server/routes/api/v1/users/[id].get.ts
import { defineEventHandler } from 'h3';
export default defineEventHandler(async (event) => {
const id = event.context.params?.['id'];
// TODO: fetch user by id
return `User profile of ${id}!`;
});
POST
// /server/routes/api/v1/users.post.ts
import { defineEventHandler, readBody } from 'h3';
export default defineEventHandler(async (event) => {
const body = await readBody(event);
// TODO: Handle body and add user
return { updated: true };
});
The h3 JSDocs provide more info and utilities for headers, cookies, redirects, and more.
Requests with Query Parameters
Sample query /api/v1/query?param1=Analog¶m2=Angular
// routes/api/v1/query.ts
import { defineEventHandler } from 'h3';
export default defineEventHandler((event) => {
const param1 = event.url.searchParams.get('param1');
const param2 = event.url.searchParams.get('param2');
return `Hello, ${param1} and ${param2}!`;
});
Catch-all Routes
Catch-all routes are helpful for fallback route handling.
// routes/api/[...].ts
import { defineEventHandler } from 'h3';
export default defineEventHandler(() => `Default page`);
Error Handling
If no errors are thrown, a status code of 200 OK will be returned. Any uncaught errors will return a 500 Internal Server Error HTTP Error.
To return other error codes, throw an exception with createError.
// routes/api/v1/[id].ts
import { createError, defineEventHandler } from 'h3';
export default defineEventHandler((event) => {
const param = event.context.params?.['id'];
const id = Number.parseInt(param ?? '', 10);
if (!Number.isInteger(id)) {
throw createError({
statusCode: 400,
statusMessage: 'ID should be an integer',
});
}
return `ID is ${id}`;
});
Accessing Cookies
Analog allows setting and reading cookies in your server-side calls.
Setting cookies
//(home).server.ts
import { PageServerLoad } from '@analogjs/router';
import { setCookie } from 'h3';
import { Product } from '../products';
export const load = async ({ fetch, event }: PageServerLoad) => {
setCookie(event, 'products', 'loaded', {
path: '/',
});
const products = await fetch<Product[]>('/api/v1/products');
return {
products: products,
};
};
Reading cookies
//index.server.ts
import { PageServerLoad } from '@analogjs/router';
import { getCookie } from 'h3';
export const load = async ({ event }: PageServerLoad) => {
const productsCookie = getCookie(event, 'products');
console.log('products cookie', productsCookie);
return {
shipping: true,
};
};
More Info
API routes are powered by Nitro and h3. See the Nitro and h3 docs for more examples around building API routes.