import { InstallTabs } from '@site/src/components/InstallTabs';

# Next.js Server

## Installation

<InstallTabs packageName="@ts-rest/next" />

## Usage

For maximum type safety, by default `@ts-rest/next` uses a single endpoint for all routes.

:::info
It is possible to split out to multiple endpoints by having multiple [...ts-rest].tsx files in different folders, so long as the routers you use all begin with the same path e.g. all `/posts` endpoints in a posts folder.
:::

```typescript
// pages/api/[...ts-rest].tsx
import { createNextRoute, createNextRouter } from '@ts-rest/next';

// `contract` is the AppRouter returned by `c.router`
const postsRouter = createNextRoute(contract.posts, {
  createPost: async (args) => {
    const newPost = await posts.createPost(args.body);

    return {
      status: 201,
      body: newPost,
    };
  },
});

const router = createNextRoute(contract, {
  posts: postsRouter,
});

// Actually initiate the collective endpoints
export default createNextRouter(contract, router);
```

`createNextRouter` is a function that takes a contract and a complete router, and creates endpoints, with the correct methods, paths and callbacks.

It's also possible to create a handler for a single contract route.

```typescript
// pages/api/posts/index.tsx
import { createSingleRouteHandler } from '@ts-rest/next';

export default createSingleRouteHandler(api.posts.createPost, async (args) => {
  const newPost = await posts.createPost(args.body);

  return {
    status: 201,
    body: newPost,
  };
});
```

`createSingleRouteHandler` is a function that takes a single contract route and a handler, and creates a `nextApiHandler`

### JSON Query Parameters

To handle JSON query parameters, you can use the `jsonQuery` option.

```typescript
export default createNextRouter(contract, router, { jsonQuery: true });
```

### Response Validation

To enable response parsing and validation, you can use the `validateResponses` option.
If there is a corresponding response Zod schema defined in the contract for the returned status code, the response will be parsed and validated.
If validation fails a `ResponseValidationError` will be thrown causing a 500 response to be returned.

```typescript
export default createNextRouter(contract, router, { validateResponses: true });
```

### Error Handling

You can create a global error handler to handle any thrown errors by using the `errorHandler` option.
This includes response validation errors.

```typescript
export default createNextRouter(contract, router, {
  responseValidation: true,
  errorHandler: (error: unknown, req: NextApiRequest, res: NextApiResponse) => {
    if (error instanceof ResponseValidationError) {
      console.log(error.cause);
      return res.status(500).json({ message: 'Internal Server Error' });
    }
  },
});
```

#### Request Validation Error Handling

The default behavior when validating request schema is to return a 400 status with the corresponding ZodError.
To customize handling of the request validation errors enable the `throwRequestValidation` option.
This option allows you to handle the request validation error in the global `errorHandler` function.

```typescript
export default createNextRouter(contract, router, {
  throwRequestValidation: true,
  errorHandler: (error: unknown, req: NextApiRequest, res: NextApiResponse) => {
    if (error instanceof RequestValidationError) {
      if (error.body !== null) {
        return res
          .status(400)
          .json({ message: 'Malformed Body', errors: error.body.flatten() });
      }

      return res.status(400).json({ message: 'Bad Request' });
    }
  },
});
```

```typescript
export class RequestValidationError extends Error {
  constructor(
    public pathParams: z.ZodError | null,
    public headers: z.ZodError | null,
    public query: z.ZodError | null,
    public body: z.ZodError | null,
  ) {
    super('[ts-rest] request validation failed');
  }
}
```
