How to Create New API Endpoints in Open-SEO: A Step-by-Step Guide
You create new API endpoints in Open-SEO by defining a TanStack Router route file under src/routes/api/, implementing the logic with createServerFn, and regenerating src/routeTree.gen.ts to register the new route.
The Open-SEO project uses TanStack Router for its HTTP API layer, where every route—including API endpoints—lives under the src/routes/ directory and is compiled into the auto-generated src/routeTree.gen.ts file. This file-based routing system enables type-safe API development with automatic route discovery.
Understanding the Routing Architecture
Open-SEO leverages TanStack Router's convention-based routing system provided by @tanstack/router-cli. All API routes follow a predictable pattern where each endpoint is defined in its own route.ts file within the src/routes/api/ hierarchy. The build process scans these files and updates src/routeTree.gen.ts accordingly.
The Route File Convention
Each API endpoint requires a dedicated directory containing a route.ts file. For example, to create the endpoint /api/example, you create src/routes/api/example/route.ts. The folder structure directly maps to the URL path, and the file must export a Route constant that TanStack Router consumes.
Step-by-Step Guide to Creating API Endpoints
1. Create the Route Folder and File
Create a new directory under src/routes/api/ matching your desired endpoint path. For an endpoint at /api/example, create the file src/routes/api/example/route.ts.
2. Export the Route Definition
The route file must export a constant named Route instantiated from TanStack Router. This object requires an id, URL path, and a serverFn property pointing to your server function.
// src/routes/api/example/route.ts
import { Route } from '@tanstack/router';
import { createServerFn } from '@tanstack/start';
export const exampleFn = createServerFn('GET', async () => {
return { ok: true, message: 'Hello from /api/example' };
});
export const Route = new Route({
id: '/api/example',
path: '/api/example',
serverFn: exampleFn,
});
Key requirements:
- The
idandpathmust match the desired URL exactly. - Use
createServerFnfor GET requests orcreateServerActionfor POST/PUT mutations. - The return value is automatically serialized to JSON and sent as the HTTP response.
3. Add Type Definitions
If your endpoint accepts complex inputs or returns custom shapes, define Zod schemas or TypeScript interfaces in src/types/ and import them into your route file for runtime validation.
4. Regenerate the Route Tree
After creating the route file, run the code generation script to update src/routeTree.gen.ts:
pnpm run generate:router
This command uses @tanstack/router-cli to scan src/routes/**/route.ts files and regenerate the route tree imports. The generated file will contain an import similar to existing API routes:
import { Route as ApiExampleRouteImport } from './routes/api/example/route';
const ApiExampleRoute = ApiExampleRouteImport.update({
id: '/api/example',
path: '/api/example',
getParentRoute: () => rootRouteImport,
} as any);
5. Apply Authentication Middleware
Most API routes in Open-SEO are protected by the ensureUser middleware located in src/middleware/ensure-user.ts. To secure your endpoint, wrap the server function with this middleware:
import { ensureUser } from '../../middleware/ensure-user';
export const profileFn = createServerFn('GET', async ({ request, ctx }) => {
const user = await ensureUser(request, ctx);
return { ok: true, email: user.email };
});
6. Write and Run Tests
Create test files adjacent to your route (e.g., src/routes/api/example/route.test.ts) or add to the existing test suite. Use fetch or TanStack's createTestRouter utilities to verify both success cases and authentication errors. Run the full suite with:
pnpm test
Practical Code Examples
Simple GET Endpoint
The following example in src/routes/api/ping/route.ts implements a basic health check:
// src/routes/api/ping/route.ts
import { Route } from '@tanstack/router';
import { createServerFn } from '@tanstack/start';
export const pingFn = createServerFn('GET', async () => ({
ok: true,
timestamp: Date.now(),
}));
export const Route = new Route({
id: '/api/ping',
path: '/api/ping',
serverFn: pingFn,
});
POST Endpoint with Zod Validation
For endpoints requiring input validation, use Zod schemas within the server function:
// src/routes/api/slug/route.ts
import { Route } from '@tanstack/router';
import { createServerFn } from '@tanstack/start';
import { z } from 'zod';
const bodySchema = z.object({
url: z.string().url(),
});
export const slugFn = createServerFn('POST', async ({ request }) => {
const json = await request.json();
const { url } = bodySchema.parse(json);
const slug = url.split('//')[1].replace(/[/.]/g, '-');
return { ok: true, slug };
});
export const Route = new Route({
id: '/api/slug',
path: '/api/slug',
serverFn: slugFn,
});
Protected User Profile Endpoint
Combine server functions with authentication middleware to create protected routes:
// src/routes/api/user/profile/route.ts
import { Route } from '@tanstack/router';
import { createServerFn } from '@tanstack/start';
import { ensureUser } from '../../middleware/ensure-user';
export const profileFn = createServerFn('GET', async ({ request, ctx }) => {
const user = await ensureUser(request, ctx);
return { ok: true, email: user.email };
});
export const Route = new Route({
id: '/api/user/profile',
path: '/api/user/profile',
serverFn: profileFn,
});
Key Files in the API Architecture
Understanding these core files helps when extending the Open-SEO API:
src/routeTree.gen.ts— The auto-generated route map that stitches together all API and client routes. Never edit this manually; regenerate it using the router CLI.src/routes/api/**/route.ts— The location where each API endpoint is declared with itsRouteobject and server function implementation.src/middleware/ensure-user.ts— The authentication middleware that enforces logged-in user sessions on protected API routes.package.json— Contains thegenerate:routerscript that rebuilds the route tree when new endpoints are added.src/types/**/*.ts— Shared TypeScript definitions and Zod schemas for request/response validation across multiple endpoints.
Summary
- Create route files under
src/routes/api/following the URL path structure you want to expose. - Implement logic using
createServerFnfrom@tanstack/start, which handles server-side execution and JSON serialization automatically. - Regenerate routes by running
pnpm run generate:routerto updatesrc/routeTree.gen.tswith your new endpoint. - Secure endpoints by importing and calling the
ensureUsermiddleware fromsrc/middleware/ensure-user.ts. - Validate inputs using Zod schemas or TypeScript interfaces stored in
src/types/for runtime type safety.
Frequently Asked Questions
What is TanStack Router and why does Open-SEO use it for API endpoints?
TanStack Router provides a type-safe, file-based routing system that handles both client-side navigation and server-side API routes. Open-SEO uses it because it automatically generates route trees, provides end-to-end type safety, and supports server functions through @tanstack/start, eliminating the need for a separate API framework.
Do I need to manually edit the routeTree.gen.ts file?
No. The src/routeTree.gen.ts file is auto-generated by the @tanstack/router-cli through the generate:router npm script. You should never manually edit this file; instead, create your route files under src/routes/ and run the generation command to update the tree automatically.
How do I secure a new API endpoint with authentication?
Import the ensureUser middleware from src/middleware/ensure-user.ts and call it at the beginning of your server function. This middleware validates the session and returns the user object, or throws an authentication error if the request lacks valid credentials.
What testing approach does Open-SEO recommend for new API routes?
Create test files adjacent to your route (e.g., src/routes/api/example/route.test.ts) or add to the existing test suite. Use standard fetch calls or TanStack's createTestRouter utilities to invoke endpoints, verifying both successful responses and proper error handling for authentication failures or validation errors.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →