---
title: Node HTTP
description: Use rest-rpc with Node.js HTTP API
---

## Install

```sh
pnpm add @rest-rpc/node
```

## Usage

### Create a handler

**Contract-first approach**

```ts
import { createRouteHandler, implement } from "@rest-rpc/node";
import { api } from "./contract";

const routes = {
	todos: {
		get: implement(api).todos.get.handler(({ id }) => getTodo(id)),
	},
};

const handle = createRouteHandler(routes);
```

**Server-first approach**

```ts
import { createServer } from "node:http";
import { createRouteHandler, route } from "@rest-rpc/node";

const routes = {
	health: route.get("/health").handler(() => ({ status: 204 })),
};
const handle = createRouteHandler(routes);
```

### Integrate with a framework

**Node HTTP**

```ts
import { createServer } from "node:http";
import { createRouteHandler } from "@rest-rpc/node";
import { routes } from "./routes";

const handle = createRouteHandler(routes);

const server = createServer(async (req, res) => {
	const { matched } = await handle(req, res);

	if (matched) {
		return;
	}

	res.statusCode = 404;
	res.end("Not found");
});

server.listen(3000);
```

**Express**

```ts
import express from "express";
import { createRouteHandler } from "@rest-rpc/node";
import { routes } from "./routes";

const app = express();
const handle = createRouteHandler(routes);

app.use(async (req, res, next) => {
	const { matched } = await handle(req, res);

	if (matched) {
		return;
	}

	next();
});

app.listen(3000);
```

**Koa**

```ts
import Koa from "koa";
import { createRouteHandler } from "@rest-rpc/node";
import { routes } from "./routes";

const app = new Koa();
const handle = createRouteHandler(routes);

app.use(async (ctx, next) => {
	ctx.respond = false;
	const { matched } = await handle(ctx.req, ctx.res);
	if (matched) {
		return;
	}
	await next();
});

app.listen(3000);
```
## Framework context

Pass application context as the third argument to `handle`:

```ts
declare module "@rest-rpc/node" {
	interface DefaultContext {
		user: { id: string };
	}
}

const result = await handle(req, res, { user });
```

Handlers also receive `context.signal`. It is aborted when the request aborts
or the response closes prematurely. Add `req` or `res` to the context when
handlers need the native objects.

## Options

```ts
type CreateNodeHandlerOptions = {
	bodyParser?: NodeBodyParser;
	requestValidationErrorHandler?: RequestValidationErrorHandler;
	responseValidationErrorHandler?: ResponseValidationErrorHandler;
};
```

### Body parsing

```ts
const handle = createRouteHandler(routes, {
	bodyParser: async (request) => readCustomBody(request),
});
```

### Error handling

Request validation errors default to a 400 response. Response contract
validation errors default to a generic 500 response. The validation handlers
receive the native Node request and response. Other errors reject the handler
promise and should be handled at the server boundary.

```ts
const handle = createRouteHandler(routes, {
	requestValidationErrorHandler: (error, req, res) => {
		res.statusCode = 422;
		res.setHeader("content-type", "application/json");
		res.end(JSON.stringify({ code: "VALIDATION_ERROR", issues: error.issues }));
	},
	responseValidationErrorHandler: (_error, req, res) => {
		res.statusCode = 500;
		res.setHeader("content-type", "application/json");
		res.end(JSON.stringify({ code: "INVALID_RESPONSE" }));
	},
});

const server = createServer(async (req, res) => {
	try {
		await handle(req, res);
	} catch (error) {
		res.statusCode = 500;
		res.end(JSON.stringify({ code: "INTERNAL_SERVER_ERROR" }));
	}
});

server.listen(3000);
```
