You're Invited: Meet the Socket team at BSidesSF and RSAC - April 27 - May 1.RSVP
Socket
Sign inDemoInstall
Socket

next-rest-framework

Package Overview
Dependencies
Maintainers
1
Versions
85
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

next-rest-framework

Next REST Framework - Type-safe, self-documenting APIs for Next.js

5.1.4-beta.1
Source
npm
Version published
Weekly downloads
1.9K
-9.59%
Maintainers
1
Weekly downloads
 
Created
Source


Next REST Framework

Type-safe, self-documenting APIs for Next.js


CI status Github Stars License Contributor Covenant 2.1

Table of contents

Overview

Next REST Framework is an open-source, opinionated, lightweight, easy-to-use set of tools to build type-safe, self-documenting APIs with Next.js. Building OpenAPI specification-compliant APIs can be cumbersome and slow but Next REST Framework makes this easy with auto-generated OpenAPI documents and docs using TypeScript and object schemas.

This is a monorepo containing the following packages / projects:

  • The primary next-rest-framework package
  • An example application for live demo and local development

Features

Lightweight, type-safe, easy to use

  • Designed to work with TypeScript so that your requests and responses are strongly typed.
  • Supports API endpoints using both RESTful and RPC principles.
  • Object-schema validation with Zod. The object schemas are automatically converted to JSON schema format for the auto-generated OpenAPI specification.
  • Auto-generated and extensible openapi.json spec file from your business logic.
  • Auto-generated Redoc and/or SwaggerUI documentation frontend.
  • Works with Next.js Middleware and other server-side libraries, like NextAuth.js.
  • Supports both Next.js app router and pages router, even at the same time.
  • Supports Edge runtime.
  • Fully customizable and compatible with any existing Next.js project.

Requirements

In order to use Next REST Framework you need to have a Next.js project with the following dependencies installed:

Installation

npm install next-rest-framework

Getting started

Create docs endpoint

To get access to the auto-generated documentation, initialize the docs endpoint somewhere in your codebase. You can also skip this step if you don't want to expose a public API documentation.

App router docs route:

// src/app/api/route.ts

import { docsRoute } from 'next-rest-framework';

export const { GET } = docsRoute();

Pages router docs API route:

// src/pages/api.ts

import { docsApiRoute } from 'next-rest-framework';

export default docsApiRoute();

This is enough to get you started. Now you can access the API documentation in your browser. Running npx next-rest-framework generate in the project root will generate the openapi.json OpenAPI specification file, located in the public folder. You can create multiple docs endpoints if needed and specify which config to use for the CLI. See the full configuration options of this endpoint in the Docs handler options section.

Create endpoint

REST endpoints

App router route:
// src/app/api/todos/route.ts

import { TypedNextResponse, route, routeOperation } from 'next-rest-framework';
import { z } from 'zod';

const TODOS = [
  {
    id: 1,
    name: 'TODO 1',
    completed: false
  }
];

// Example app router route handler with GET/POST handlers.
export const { GET, POST } = route({
  getTodos: routeOperation({
    method: 'GET',
    // Optional OpenAPI operation documentation.
    openApiOperation: {
      tags: ['example-api', 'todos', 'app-router']
    }
  })
    // Output schema for strictly-typed responses and OpenAPI documentation.
    .outputs([
      {
        status: 200,
        contentType: 'application/json',
        schema: z.array(
          z.object({
            id: z.number(),
            name: z.string(),
            completed: z.boolean()
          })
        )
      }
    ])
    .handler(() => {
      // Type-checked response.
      return TypedNextResponse.json(TODOS, {
        status: 200
      });
    }),

  createTodo: routeOperation({
    method: 'POST',
    // Optional OpenAPI operation documentation.
    openApiOperation: {
      tags: ['example-api', 'todos', 'app-router']
    }
  })
    // Input schema for strictly-typed request, request validation and OpenAPI documentation.
    .input({
      contentType: 'application/json',
      body: z.object({
        name: z.string()
      })
    })
    // Output schema for strictly-typed responses and OpenAPI documentation.
    .outputs([
      {
        status: 201,
        contentType: 'application/json',
        schema: z.string()
      },
      {
        status: 401,
        contentType: 'application/json',
        schema: z.string()
      }
    ])
    .middleware(
      // Optional middleware logic executed before request validation.
      (req) => {
        if (!req.headers.get('authorization')) {
          // Type-checked response.
          return TypedNextResponse.json('Unauthorized', {
            status: 401
          });
        }
      }
    )
    .handler(async (req) => {
      const { name } = await req.json(); // Strictly-typed request.

      // Type-checked response.
      return TypedNextResponse.json(`New TODO created: ${name}`, {
        status: 201
      });
    })
});

The TypedNextResponse ensures that the response status codes and content-type headers are type-checked. You can still use the regular NextResponse if you prefer to have less type-safety.

Pages router API route:
// src/pages/api/todos.ts

import { apiRoute, apiRouteOperation } from 'next-rest-framework';
import { z } from 'zod';

const TODOS = [
  {
    id: 1,
    name: 'TODO 1',
    completed: false
  }
];

// Example pages router API route with GET/POST handlers.
export default apiRoute({
  getTodos: apiRouteOperation({
    method: 'GET',
    // Optional OpenAPI operation documentation.
    openApiOperation: {
      tags: ['example-api', 'todos', 'pages-router']
    }
  })
    // Output schema for strictly-typed responses and OpenAPI documentation.
    .outputs([
      {
        status: 200,
        contentType: 'application/json',
        schema: z.array(
          z.object({
            id: z.number(),
            name: z.string(),
            completed: z.boolean()
          })
        )
      }
    ])
    .handler((_req, res) => {
      // Type-checked response.
      res.status(200).json(TODOS);
    }),

  createTodo: apiRouteOperation({
    method: 'POST',
    // Optional OpenAPI operation documentation.
    openApiOperation: {
      tags: ['example-api', 'todos', 'pages-router']
    }
  })
    // Input schema for strictly-typed request, request validation and OpenAPI documentation.
    .input({
      contentType: 'application/json',
      body: z.object({
        name: z.string()
      })
    })
    // Output schema for strictly-typed responses and OpenAPI documentation.
    .outputs([
      {
        status: 201,
        contentType: 'application/json',
        schema: z.string()
      },
      {
        status: 401,
        contentType: 'application/json',
        schema: z.string()
      }
    ])
    // Optional middleware logic executed before request validation.
    .middleware((req, res) => {
      if (!req.headers.authorization) {
        res.status(401).json('Unauthorized'); // Type-checked response.
      }
    })
    .handler((req, res) => {
      const { name } = req.body; // Strictly-typed request.
      res.status(201).json(`New TODO created: ${name}`); // Type-checked response.
    })
});

After running next-rest-framework generate, all of above type-safe endpoints will be auto-generated to your OpenAPI spec and exposed in the documentation:

Next REST Framework docs

RPC endpoints

App router RPC route:

A recommended way is to write your RPC operation in a separate server-side module where they can be consumed both by the RPC endpoints and directly as server-side functions (server actions):

// src/app/actions.ts

'use server';

import { rpcOperation } from 'next-rest-framework';
import { z } from 'zod';

const TODOS = [
  {
    id: 1,
    name: 'TODO 1',
    completed: false
  }
];

const todoSchema = z.object({
  id: z.number(),
  name: z.string(),
  completed: z.boolean()
});

export const getTodos = rpcOperation({
  tags: ['RPC']
})
  .outputs([
    {
      schema: z.array(todoSchema)
    }
  ])
  .handler(() => {
    return TODOS; // Type-checked output.
  });

export const getTodoById = rpcOperation({
  tags: ['RPC']
})
  .input(z.string())
  .outputs([
    {
      schema: z.object({
        error: z.string()
      })
    },
    {
      schema: todoSchema
    }
  ])
  .handler((id) => {
    const todo = TODOS.find((t) => t.id === Number(id));

    if (!todo) {
      return { error: 'TODO not found.' }; // Type-checked output.
    }

    return todo; // Type-checked output.
  });

export const createTodo = rpcOperation({
  tags: ['RPC']
})
  .input(
    z.object({
      name: z.string()
    })
  )
  .outputs([{ schema: todoSchema }])
  .handler(
    async ({
      name // Strictly-typed input.
    }) => {
      // Create todo.
      const todo = { id: 2, name, completed: false };
      return todo; // Type-checked output.
    }
  );

export const deleteTodo = rpcOperation({
  tags: ['RPC']
})
  .input(z.string())
  .outputs([
    { schema: z.object({ error: z.string() }) },
    { schema: z.object({ message: z.string() }) }
  ])
  .handler((id) => {
    // Delete todo.
    const todo = TODOS.find((t) => t.id === Number(id));

    if (!todo) {
      return {
        error: 'TODO not found.' // Type-checked output.
      };
    }

    return { message: 'TODO deleted.' }; // Type-checked output.
  });

The file path to and RPC route must end with /[operationId]/route.ts. Import the RPC operations in to your RPC route handler:

// src/app/api/rpc/[operationId]/route.ts

import { createTodo, deleteTodo, getTodoById, getTodos } from 'src/app/actions';
import { rpcRoute } from 'next-rest-framework';

export const { POST } = rpcRoute({
  getTodos,
  getTodoById,
  createTodo,
  deleteTodo
});

export type RpcClient = typeof POST.client;

Consume the RPC operations directly in your server-side components:

'use server';

import { getTodos, createTodo } from 'src/app/actions';

export default async function Page() {
  const todos = await getTodos();

  const createTodo = async (name: string) => {
    'use server';
    return createTodo({ name });
  };

  // ...
}
Pages router RPC route:

The filename of an RPC API route must be [operationId].ts.

// src/pages/api/rpc/[operationId].ts

import { rpcApiRoute } from 'next-rest-framework';

// Example pages router RPC handler.
const handler = rpcApiRoute({
  // ...
  // Exactly the same as the app router example. You can also inline the RPC operations in this object.
});

export default handler;

export type RpcClient = typeof handler.client;

The RPC routes will also be included in your OpenAPI spec after running next-rest-framework generate.

Client

REST client

To achieve end-to-end type-safety, you can use any client implementation that relies on the generated OpenAPI specification, e.g. openapi-client-axios.

RPC client

For client-rendered components you can use the strongly-typed rpcClient:

'use client';

import { rpcClient } from 'next-rest-framework/rpc-client';
import { type RpcClient } from 'app/api/rpc/[operationId]';

const client = rpcClient<RpcClient>({
  url: 'http://localhost:3000/api/rpc'
});

export default function Page() {
  // ...

  useEffect(() => {
    client
      .getTodos()
      .then(() => {
        // ...
      })
      .catch(console.error);
  }, []);

  const createTodo = async (name: string) => {
    const todo = client.createTodo({ name });
    // ...
  };

  // ...
}

The rpcClient calls can also be easily integrated with any data fetching framework, like React Query or RTKQ.

API reference

Docs handler options

The following options can be passed to the docsRoute (app router) and docsApiRoute (pages router) functions for customizing Next REST Framework:

NameDescription
deniedPathsArray of paths that are denied by Next REST Framework and not included in the OpenAPI spec. Supports wildcards using asterisk * and double asterisk ** for recursive matching. Example: ['/api/disallowed-path', '/api/disallowed-path-2/*', '/api/disallowed-path-3/**'] Defaults to no paths being disallowed.
allowedPathsArray of paths that are allowed by Next REST Framework and included in the OpenAPI spec. Supports wildcards using asterisk * and double asterisk ** for recursive matching. Example: ['/api/allowed-path', '/api/allowed-path-2/*', '/api/allowed-path-3/**'] Defaults to all paths being allowed.
openApiObjectAn OpenAPI Object that can be used to override and extend the auto-generated specification.
openApiJsonPathPath that will be used for fetching the OpenAPI spec - defaults to /openapi.json. This path also determines the path where this file will be generated inside the public folder.
docsConfigA Docs config object for customizing the generated docs.
suppressInfoSetting this to true will suppress all informational logs from Next REST Framework. Defaults to false.

Docs config

The docs config options can be used to customize the generated docs:

NameDescription
providerDetermines whether to render the docs using Redoc (redoc) or SwaggerUI swagger-ui. Defaults to redoc.
titleCustom title, used for the visible title and HTML title.
descriptionCustom description, used for the visible description and HTML meta description.
faviconUrlCustom HTML meta favicon URL.
logoUrlA URL for a custom logo.
ogConfigBasic customization options for OG meta tags. Requires the following fields: title, type, url, imageUrl.

REST

Route handler options

The following options can be passed to the routeHandler (app router) and apiRouteHandler (pages router) functions to create new API endpoints:

NameDescriptionRequired
GET | PUT | POST | DELETE | OPTIONS | HEAD | PATCHA Method handler object.true
openApiPathAn OpenAPI Path Item Object that can be used to override and extend the auto-generated specification.false

Route operations

The route operation functions routeOperation (app router) and apiRouteOperation (pages router) allow you to define your API handlers for your endpoints. These functions accept an OpenAPI Operation object as a parameter, that can be used to override the auto-generated specification. Calling this function allows you to chain your API handler logic with the following functions.

NameDescription
inputA Route operation input function for defining the validation and documentation of the request.
outputsAn Route operation outputs function for defining the validation and documentation of the response.
handlerA Route operation-handler function for defining your business logic.
middlewareA Route operation middleware function that gets executed before the request input is validated. You may chain up to three middlewares together and share data between the middlewares by taking the input of the previous middleware.
Route operation input

The route operation input function is used for type-checking, validation and documentation of the request, taking in an object with the following properties:

NameDescriptionRequired
contentTypeThe content type header of the request. When the content type is defined, a request with an incorrect content type header will get an error response.false
bodyA Zod schema describing the format of the request body. When the body schema is defined, a request with an invalid request body will get an error response.false
queryA Zod schema describing the format of the query parameters. When the query schema is defined, a request with invalid query parameters will get an error response.false

Calling the route operation input function allows you to chain your API handler logic with the Route operation outputs, Route operation middleware and Route operation handler functions.

Route operation outputs

The route operation outputs function is used for type-checking and documentation of the response, taking in an array of objects with the following properties:

NameDescriptionRequired
statusA status code that your API can return.true
contentTypeThe content type header of the response.true
schemaA Zod schema describing the format of the response data. true

Calling the route operation outputs function allows you to chain your API handler logic with the Route operation middleware and Route operation handler functions.

Route operation middleware

The route operation middleware function is executed before validating the request input. The function takes in the same parameters as the Next.js router handlers and API routes handlers. Additionally, as a second parameter this function takes the return value of your last middleware function, defaulting to an empty object. Throwing an error inside a middleware function will stop the execution of the handler and you can also return a custom response like you would do within the Handler function. Calling the route operation middleware function allows you to chain your API handler logic with the Handler function. Alternatively, you may chain up to three middleware functions together:

// ...
const handler = route({
  getTodos: routeOperation()
    .middleware(() => {
      return { foo: 'bar' };
    })
    .middleware((_req, _ctx, { foo }) => {
      // if (myCondition) {
      //   return NextResponse.json({ error: 'My error.' });
      // }

      return {
        foo,
        bar: 'baz'
      };
    })
    .handler((_req, _ctx, { foo, bar }) => {
      // ...
    })
});
Route operation handler

The route operation handler function is a strongly-typed function to implement the business logic for your API. The function takes in strongly-typed versions of the same parameters as the Next.js router handlers and API routes handlers. Additionally, as a third parameter this function takes the return value of your last middleware function:

// ...
const handler = route({
  getTodos: routeOperation()
    .middleware(() => {
      return { foo: "bar" };
    })
    .handler((_req, _ctx, { foo }) => {
      // ...
    });
});

RPC

RPC route handler options

The rpcRouteHandler (app router) and rpcApiRouteHandler (pages router) functions allow the following options as the second parameter after passing your RPC operations.

NameDescriptionRequired
openApiPathAn OpenAPI Path Item Object that can be used to override and extend the auto-generated specification.false
openApiOperationAn OpenAPI Path Item Object that can be used to override and extend the auto-generated specification.false

RPC operations

The rpcOperation function allows you to define your API handlers for your RPC endpoint. Calling this function allows you to chain your API handler logic with the following functions.

NameDescription
inputAn RPC operation input function for defining the validation and documentation of the operation.
outputsAn RPC operation outputs function for defining the validation and documentation of the response.
handlerAn RPC operation handler function for defining your business logic.
middlewareAn RPC operation middleware function that gets executed before the operation input is validated. You may chain up to three middlewares together and share data between the middlewares by taking the input of the previous middleware.
RPC operation input

The RPC operation input function is used for type-checking, validation and documentation of the RPC call. It takes in a A Zod schema as a parameter that describes the format of the operation input. When the input schema is defined, an RPC call with invalid input will get an error response.

Calling the RPC input function allows you to chain your API handler logic with the RPC operation outputs, RPC middleware and RPC handler functions.

RPC operation outputs

The RPC operation outputs function is used for type-checking and documentation of the response, taking in an array of objects with the following properties:

NameDescriptionRequired
schemaA Zod schema describing the format of the response data. true
nameAn optional name used in the generated OpenAPI spec, e.g. GetTodosErrorResponse.false

Calling the RPC operation outputs function allows you to chain your API handler logic with the RPC operation middleware and RPC operation handler functions.

RPC operation middleware

The RPC operation middleware function is executed before validating RPC operation input. The function takes in strongly typed parameters typed by the RPC operation input function. Additionally, as a second parameter this function takes the return value of your last middleware function, defaulting to an empty object. Throwing an error inside a middleware function will stop the execution of the handler. Calling the RPC operation middleware function allows you to chain your RPC API handler logic with the RPC operation handler function. Alternatively, you may chain up to three middleware functions together:

// ...
const handler = rpcRoute({
  getTodos: rpcOperation()
    .middleware(() => {
      return { foo: 'bar' };
    })
    .middleware((_input, { foo }) => {
      // if (myCondition) {
      //   throw Error('My error.')
      // }

      return {
        foo,
        bar: 'baz'
      };
    })
    .handler((_input, { foo, bar }) => {
      // ...
    })
});
RPC operation handler

The RPC operation handler function is a strongly-typed function to implement the business logic for your API. The function takes in strongly typed parameters typed by the RPC operation input function. Additionally, as a second parameter this function takes the return value of your last middleware function:

// ...
const handler = rpcApiRoute({
  getTodos: rpcOperation()
    .middleware(() => {
      return { foo: "bar" };
    })
    .handler((_input, { foo }) => {
      // ...
    });
});

CLI

The Next REST Framework CLI supports generating and validating the openapi.json file:

  • npx next-rest-framework generate to generate the openapi.json file.
  • npx next-rest-framework validate to validate that the openapi.json file is up-to-date.

The next-rest-framework validate command is useful to have as part of the static checks in your CI/CD pipeline. Both commands support the following options:

NameDescription
--configPath <string>In case you have multiple docs handlers with different configurations, you can specify which configuration you want to use by providing the path to the API. Example: /api/my-configuration.

A good practice is to set these in your package.json as both commands are needed:

// package.json
...
"scripts": {
  ...
  "generate": "next-rest-framework generate",
  "validate": "next-rest-framework validate",
}

Changelog

See the changelog in CHANGELOG.md

Contributing

All contributions are welcome!

License

ISC, see full license in LICENSE.

Keywords

nextjs

FAQs

Package last updated on 20 Feb 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts