Skip to main content
Get a working API server up and running with I/O validation in just a few minutes.
Already installed Express Zod API? If not, follow the installation guide first.

Step 1: Configure TypeScript

Make sure your tsconfig.json has strict mode enabled:
tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "skipLibCheck": true
  }
}

Step 2: Create Configuration

Create a configuration file for your server:
config.ts
import { createConfig } from "express-zod-api";

const config = createConfig({
  http: { listen: 8090 },
  cors: true,
});

export default config;
http.listen
number
Port number for the HTTP server
cors
boolean
Enable cross-origin resource sharing

Step 3: Create Your First Endpoint

Create an endpoint that responds with a greeting:
endpoints.ts
import { defaultEndpointsFactory } from "express-zod-api";
import { z } from "zod";

export const helloEndpoint = defaultEndpointsFactory.build({
  method: "get",
  input: z.object({
    name: z.string().optional(),
  }),
  output: z.object({
    greetings: z.string(),
  }),
  handler: async ({ input: { name }, logger }) => {
    logger.debug(`Greeting ${name || "World"}`);
    return { greetings: `Hello, ${name || "World"}. Happy coding!` };
  },
});

What’s Happening Here?

1

Input Schema

The input defines what data the endpoint accepts. Here, an optional name string from the query parameters.
2

Output Schema

The output defines what data the endpoint returns. The response is validated against this schema.
3

Handler Function

The async handler receives validated input and returns validated output. TypeScript knows the exact types!

Step 4: Set Up Routing

Connect your endpoint to a URL path:
routing.ts
import { Routing } from "express-zod-api";
import { helloEndpoint } from "./endpoints";

const routing: Routing = {
  v1: {
    hello: helloEndpoint,
  },
};

export default routing;
This creates the route GET /v1/hello.

Step 5: Start the Server

Create your server entry point:
index.ts
import { createServer } from "express-zod-api";
import config from "./config";
import routing from "./routing";

createServer(config, routing);

Step 6: Test Your API

Start your server:
tsx index.ts
# or: node --loader tsx index.ts
# or: ts-node index.ts
Test with curl:
curl "http://localhost:8090/v1/hello?name=Rick"
You should see:
{
  "status": "success",
  "data": {
    "greetings": "Hello, Rick. Happy coding!"
  }
}

Complete Example

Here’s everything in one file:
server.ts
import { createServer, createConfig, defaultEndpointsFactory, Routing } from "express-zod-api";
import { z } from "zod";

// Configuration
const config = createConfig({
  http: { listen: 8090 },
  cors: true,
});

// Endpoint
const helloEndpoint = defaultEndpointsFactory.build({
  method: "get",
  input: z.object({
    name: z.string().optional(),
  }),
  output: z.object({
    greetings: z.string(),
  }),
  handler: async ({ input: { name } }) => ({
    greetings: `Hello, ${name || "World"}. Happy coding!`,
  }),
});

// Routing
const routing: Routing = {
  v1: {
    hello: helloEndpoint,
  },
};

// Start server
createServer(config, routing);

What’s Next?

Common Next Steps

Add a POST Endpoint

const createUserEndpoint = defaultEndpointsFactory.build({
  method: "post",
  input: z.object({
    name: z.string().min(1),
    email: z.string().email(),
  }),
  output: z.object({
    id: z.string(),
    name: z.string(),
    email: z.string(),
  }),
  handler: async ({ input }) => {
    // Save to database
    const user = await db.users.create(input);
    return user;
  },
});

Add Path Parameters

const routing: Routing = {
  v1: {
    users: {
      ":id": getUserEndpoint, // GET /v1/users/:id
    },
  },
};

const getUserEndpoint = defaultEndpointsFactory.build({
  method: "get",
  input: z.object({
    id: z.string(), // from path parameter
  }),
  output: z.object({
    id: z.string(),
    name: z.string(),
  }),
  handler: async ({ input: { id } }) => {
    return await db.users.findById(id);
  },
});

Handle Errors

import createHttpError from "http-errors";

const endpoint = defaultEndpointsFactory.build({
  handler: async ({ input }) => {
    const user = await db.users.findById(input.id);
    
    if (!user) {
      throw createHttpError(404, "User not found");
    }
    
    return user;
  },
});

Troubleshooting

If port 8090 is already in use, change it in your config:
const config = createConfig({
  http: { listen: 3000 }, // Use a different port
  cors: true,
});
Make sure you have strict: true in your tsconfig.json and all required dependencies installed:
pnpm add -D @types/express @types/node @types/http-errors
Enable CORS in your configuration:
const config = createConfig({
  http: { listen: 8090 },
  cors: true, // Enable CORS
});