npm package: @routepact/server
Description: Express adapter for type-safe route pacts — endpoint builders, request/response validation middleware, and router setup
Latest version: 0.1.5
Deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.

# @routepact/server

Express adapter for `@routepact/core` pacts. Provides type-safe endpoint builders, request/response validation middleware, and a versioned router factory.

## Installation

```bash
npm install @routepact/server @routepact/core express zod
npm install -D @types/express
```

## Core concepts

### `defineEndpoint`

Wraps a handler with a pact, giving the `req` and `res` objects types inferred directly from the pact's Zod schemas.

```ts
import { defineEndpoint } from "@routepact/server";
import { PostPacts } from "../shared/pacts/post.pact";

const createPost = defineEndpoint({
  pact: PostPacts.create,
  handler: async (req, res) => {
    // req.body is typed as { title: string; body: string }
    const { title, body } = req.body;

    const post = await db.posts.insert({ title, body });

    // res.json only accepts the shape defined by PostPacts.create.response
    res.status(201).json(post);
  },
});
```

If the pact path has parameters, `req.parsedParams` is automatically populated from `req.params` and typed from the path string. If the pact has a `query` Zod schema, the validated value is available as `req.parsedQueries`:

```ts
const getPostById = defineEndpoint({
  pact: PostPacts.getById, // path: "/posts/:id"
  handler: (req, res) => {
    // req.parsedParams is typed as { id: string }
    const { id } = req.parsedParams;
    // ...
  },
});

const listPosts = defineEndpoint({
  pact: PostPacts.list, // has query: z.object({ page: z.string().optional() })
  handler: (req, res) => {
    // req.parsedQueries is typed as { page?: string }
    const { page } = req.parsedQueries;
    // ...
  },
});
```

### `createRouter`

Takes a config with a `basePath` and route groups and returns an Express router mounted at that path.

```ts
import { createRouter } from "@routepact/server";
import {
  createPost,
  getPostById,
  listPosts,
  updatePost,
  deletePost,
} from "./post.endpoints";

export const postRouter = createRouter({
  basePath: "/api/v1",
  routeGroups: [
    {
      description: "Posts",
      endpoints: [listPosts, getPostById, createPost, updatePost, deletePost],
    },
  ],
});

// Mount in your Express app
app.use(postRouter);
// Routes are now available at /api/v1/posts, /api/v1/posts/:id, etc.
```

**Options:**

```ts
createRouter(config, {
  logger: myLogger, // default: console — must have an `error` method
});
```

The router detects and throws on duplicate route registrations (`[method] path` pairs) at startup rather than silently shadowing them.

### `defineRouteGroup`

Helper to define a route group with full type checking before passing it to `createRouter`:

```ts
import { defineRouteGroup } from "@routepact/server";

const publicRoutes = defineRouteGroup({
  description: "Public routes",
  endpoints: [listPosts, getPostById],
});
```

## Middleware

### Group middleware

Apply Express middleware to every endpoint in a route group. Common use case: protecting a group of routes with an auth check.

```ts
import { createRouter } from "@routepact/server";
import { requireAuth } from "./middlewares/auth";
import { listPosts, createPost } from "./post.endpoints";

const router = createRouter({
  basePath: "/api/v1",
  routeGroups: [
    {
      description: "Public routes",
      endpoints: [listPosts],
    },
    {
      description: "Protected routes",
      middlewares: [requireAuth],
      endpoints: [createPost],
    },
  ],
});
```

### Endpoint middleware (`createEndpointMiddleware`)

Type-safe middleware that augments the `req` object. The properties it returns are merged into the request type seen by the handler.

```ts
import { createEndpointMiddleware, defineEndpoint } from "@routepact/server";
import { PostPacts } from "../shared/pacts/post.pact";
import { getSessionFromRequest } from "./auth";

// Declare what this middleware adds to req
const withUser = createEndpointMiddleware(async (req) => {
  const session = await getSessionFromRequest(req);
  if (!session) throw new Error("Unauthorized");
  return { user: session.user }; // TypeScript infers { user: User }
});

const createPost = defineEndpoint({
  pact: PostPacts.create,
  middlewares: [withUser] as const, // `as const` needed to preserve tuple type
  handler: async (req, res) => {
    // req.user is now typed as User — no casting needed
    const post = await db.posts.insert({
      ...req.body.resource,
      authorId: req.user.id,
    });
    res.status(201).json({ post });
  },
});
```

Multiple middlewares are supported and their added types are merged:

```ts
const getPost = defineEndpoint({
  pact: PostPacts.getById,
  middlewares: [withUser, withRateLimit] as const,
  handler: async (req, res) => {
    // req has both .user (from withUser) and .rateLimit (from withRateLimit)
  },
});
```

## Validation middleware

Request and response validation is applied automatically when a pact has the corresponding Zod schemas. You don't need to call these manually unless you want to use them outside of `createRouter`.

```ts
import {
  createRequestValidationMiddleware,
  createResponseValidationMiddleware,
} from "@routepact/server";
import { z } from "zod";

// Validates req.body against the schema
app.use(createRequestValidationMiddleware(z.object({ name: z.string() })));

// Validates res.json(...) before sending
app.use(
  createResponseValidationMiddleware(
    z.object({ id: z.string(), name: z.string() }),
  ),
);
```

### Validation errors

When validation fails, the middleware calls `next(error)` with one of these:

| Error class               | Status | When                                               |
| ------------------------- | ------ | -------------------------------------------------- |
| `RequestValidationError`  | 400    | `req.body` or `req.query` fails validation         |
| `ResponseValidationError` | 500    | `res.json({ resource })` fails the response schema |

Both expose a `cause` property with the raw `ZodError`. Register an error handler in your Express app to format them:

```ts
import {
  ValidationError,
  RequestValidationError,
  ResponseValidationError,
} from "@routepact/server";

app.use((err, req, res, next) => {
  if (err instanceof RequestValidationError) {
    return res
      .status(400)
      .json({ message: "Invalid request", errors: err.cause.errors });
  }
  if (err instanceof ResponseValidationError) {
    console.error("Response validation failed:", err.cause.errors);
    return res.status(500).json({ message: "Internal server error" });
  }
  // Or catch any validation error regardless of type:
  if (err instanceof ValidationError) {
    return res.status(err.status).json({ message: err.message });
  }
  next(err);
});
```

## Type reference

| Export                                                 | Description                                                                |
| ------------------------------------------------------ | -------------------------------------------------------------------------- |
| `defineEndpoint(config)`                               | Creates a typed endpoint from a pact + handler                             |
| `defineRouteGroup(group)`                              | Creates a typed route group                                                |
| `createEndpointMiddleware(handler)`                    | Creates a type-augmenting middleware                                       |
| `createRouter(config, options?)`                       | Builds a versioned Express router                                          |
| `createRequestValidationMiddleware(schema)`            | Validates `req.body.resource`                                              |
| `createResponseValidationMiddleware(resource?, meta?)` | Validates `res.json(...)` output                                           |
| `ValidationError`                                      | Base class for all validation errors — has `status` and `cause: ZodError`  |
| `RequestValidationError`                               | Extends `ValidationError` — thrown on bad request, params, or query (400)  |
| `ResponseValidationError`                              | Extends `ValidationError` — thrown on bad response body (status 500)       |
| `BaseRequest<T>`                                       | Express `Request` with typed `body`, `parsedParams`, `parsedQueries`       |
| `BackendApiResponse<TRes, TMeta>`                      | Express `Response` with typed `json()`                                     |
| `EndpointMiddleware<TAdds>`                            | A middleware that augments `req` with `TAdds`                              |
| `RouteEndpoint<TPact, TMiddlewares>`                   | Full endpoint type including handler and middlewares                       |
| `RouteGroup`                                           | A group of endpoints with optional shared middleware                       |
| `RouterConfig`                                         | Config passed to `createRouter`                                            |
| `RouterOptions`                                        | Options passed to `createRouter` — `logger`                                |
| `Logger`                                               | `Pick<Console, "error">` — the logger interface expected by `createRouter` |