First Route
This guide adds a GET /users/:userId endpoint to an existing Routa project. By the end
you will have a validated path parameter, a typed response, and a route that appears in
generated metadata and OpenAPI.
Before You Start
Section titled “Before You Start”- A Routa project created with
create-routa-ts, or an existing project set up through Installation. - The development server stopped. You will restart it in the last step.
-
Create the schema module. Keeping schemas beside the route is the default convention, and exported names become OpenAPI component names.
src/routes/users/$userId/schemas.ts import { z } from "zod";export const GetUserParams = z.object({userId: z.string().min(1),});export const GetUserResponse = z.object({id: z.string(),name: z.string(),});export const UserNotFoundResponse = z.object({message: z.string(),}); -
Create the route file. The folder path becomes the URL path, and
$userIdbecomes the:userIdparameter.src/routes/users/$userId/route.ts import { createRoute, createRouteRoot } from "@routa-ts/core";import { GetUserParams, GetUserResponse, UserNotFoundResponse } from "./schemas.js";const route = createRouteRoot("/users/:userId");export default route({get: createRoute({input: {params: GetUserParams,},responses: {success: {status: 200,schema: GetUserResponse,},notFound: {status: 404,schema: UserNotFoundResponse,},},run: async ({ input }) => {if (input.params.userId !== "usr_1") {return { type: "notFound", data: { message: "User not found." } };}return { type: "success", data: { id: input.params.userId, name: "Ada" } };},}),}); -
Generate route metadata so the new path is registered and typed.
Terminal window npm run generateTerminal window pnpm run generateTerminal window yarn run generateTerminal window bun run generateGenerated .routa/routes.gen.ts for 2 route file(s). -
Start the development server.
Terminal window npm run devTerminal window pnpm run devTerminal window yarn run devTerminal window bun run devINFO api.started Routa API started. {"host":"127.0.0.1","port":3000,"routes":2}
Verify
Section titled “Verify”Both declared outcomes are reachable, and each returns its declared status:
curl -i http://127.0.0.1:3000/users/usr_1HTTP/1.1 200 OKcontent-type: application/json; charset=utf-8
{"id":"usr_1","name":"Ada"}curl -i http://127.0.0.1:3000/users/usr_2HTTP/1.1 404 Not Foundcontent-type: application/json; charset=utf-8
{"message":"User not found."}What Just Happened
Section titled “What Just Happened”createRouteRoot("/users/:userId") binds the file to a path and gives the methods inside
it the context type generated for that path. createRoute declares one method. Routa
parses input.params through GetUserParams before run executes, so input.params.userId
is a validated string rather than string | undefined.
The handler returns { type, data } where type is a key of responses. TypeScript
rejects any other name, and at runtime Routa validates data against that outcome’s
schema before serializing it with the declared status.
Troubleshooting
Section titled “Troubleshooting”| Problem | Fix |
|---|---|
ROUTA_ROUTE_CONFIG_UNRESOLVED |
The file must have a default export produced by the createRouteRoot(path) helper. Assigning the config to a variable that is exported later cannot be resolved statically. |
ROUTA_MISSING_SUCCESS_RESPONSE |
Every method needs at least one response with a 2xx status. |
ROUTA_RESULT_TYPE |
The handler returned a name that is not a key of responses. Check for a typo or add the outcome. |
| The route returns 404 for every request. | Metadata is stale. Run routa generate and restart the server. |