Skip to content

lightweight nextjs api handler wrapper, portable & configurable for serverless environment

License

Notifications You must be signed in to change notification settings

tobysmith568/next-api-handler

This branch is 148 commits behind howard86/next-api-handler:main.

Folders and files

NameName
Last commit message
Last commit date

Latest commit

author
Conventional Changelog Action
Apr 13, 2022
05e7d27 · Apr 13, 2022
Apr 12, 2022
Feb 24, 2022
Sep 19, 2021
Apr 12, 2022
Apr 12, 2022
Jan 1, 2022
Sep 19, 2021
Sep 21, 2021
Sep 19, 2021
Sep 19, 2021
Apr 13, 2022
Sep 19, 2021
Jan 1, 2022
Apr 13, 2022
Sep 21, 2021
Sep 19, 2021
Apr 2, 2022

Repository files navigation

next-api-handler

install size CircleCI codecov next-api-handler Known Vulnerabilities

lightweight next.js api handler wrapper, portable & configurable for serverless environment

Introduction

next-api-handler contains helper functions and generic types to create next.js API routes with strong type declaration and type-checking.

next-api-handler will help in the following 3 aspects

  • sharable & express-like apis with middleware to build RESTful api route
  • simple response handler to transform response and error/exception in a predictable way
  • friendly generic types to build type-safe API interface shared between client & server

Getting Started

TL;DR

The following snippet will create /api/user following RESTful standard

// in new /pages/api/user.ts
import { RouterBuilder } from 'next-api-handler';

const router = new RouterBuilder();

router.post(async (req) => createUser(req.query.id));
router.get<{ name: string }>(() => ({ name: 'John Doe' }));

return router.build();

Requirements

Currently only tested with node.js v14+ with next.js v10+

Feel free to submit if you have issues regard to runtime environments

Installation

npm install next-api-handler
# or using yarn
yarn add next-api-handler

Features

Basic Usage

We can use express.js router-like builder

// in new /pages/api/user.js
import { RouterBuilder } from 'next-api-handler';

const router = new RouterBuilder();

router.post((req) => createUser(req.query.id));
router.get(() => ({ name: 'John Doe' }));

// or with async/await
// router.post(async (req) => {
//   const user = await createUser(req.query.id);
//   return user;
// });

return router.build();

Which is equivalent to the following next.js API routes

// in old /pages/api/user.js
export default async function handler(req, res) {
  if (req.method === 'POST') {
    const user = await createUser(req.query.id); // some async function/services
    res.status(200).json({ success: true, data: user });
  } else if (req.method === 'GET') {
    res.status(200).json({ success: true, data: { name: 'John Doe' } });
  } else {
    res.setHeader('Allow', ['GET', 'POST']);
    res.status(405).json({
      success: false,
      message: `Method ${req.method} Not Allowed`,
    });
  }
}

Please see more examples in example folder, can compare /pages/api/before/* & /pages/api/after/*

More CRUD

Please see examples in before/crud/[id].ts and after/crud/[id].ts

Handling HTTP Error Response

There are some common built-in HTTP exceptions to shorten writing up error response, this can get particularly useful when writing up complex controllers

import { RouterBuilder, BadRequestException } from 'next-api-handler';

const router = new RouterBuilder();

router.get(() => throw new BadRequestException('something went wrong'));

export default router.build();

is equivalent to do

export default function handler(_req, res) {
  return res.status(400).json({
    success: false,
    message:
      process.env.NODE_ENV === 'production'
        ? 'Bad Request'
        : 'something went wrong ',
  });
}

We can disable process.env.NODE_ENV check by passing showMessage=true when creating the router

const router = new RouterBuilder({ showMessage: true });

API Middleware

If there's a piece of shared logic before handling the request, we can create middleware with typescript support

import { UnauthorizedException, NotFoundException } from 'next-api-handler';
import type { NextApiRequest, NextApiResponse } from 'next';

const authMiddleware = async (req: NextApiRequest, res: NextApiResponse) => {
  const apiKey = req.cookies['Authorization'];

  const isValid = await verify(apiKey); // some async service to check apiKey

  if (!isValid) {
    throw new UnauthorizedException();
  }

  const user = await getUserInfo(apiKey); // another async service to fetch user

  if (!user) {
    throw new NotFoundException();
  }

  return { user };
};

then we can reuse this user value in all other routes white enforcing type-checking

import { RouterBuilder, NextApiRequestWithMiddleware } from 'next-api-handler';

// some type returned from getUserInfo
type User = {
  id: string;
};

const router = new RouterBuilder();

// or router.inject(authMiddleware) if the order of adding middleware does not matter
router.use<User>(authMiddleware);

// all middleware values will stay in `req.middleware`,
router.get<string, User>(
  (req: NextApiRequestWithMiddleware<User>) => req.middleware.user.id
);

return router.build();

Utilities

Type declaration & Type-checking

This package is carefully designed for TypesScript users to correctly enforce type definitions

For server side, we can declare return types in any of route handler

router.get<{ name: string }>(() => ({ name: 'John Doe' }));

We can share the response type for client side to reuse, e.g. here as axios

// export type ApiResponse<T = unknown> = SuccessApiResponse<T> | ErrorApiResponse;
// export type SuccessApiResponse<T> = { success: true; data: T };
// export type ErrorApiResponse = { success: false; message: string };

import axios from 'axios';
import type { SuccessApiResponse } from 'next-api-handler';

type User = { name: string };

const result = await axios.get<SuccessApiResponse<User>>('/api/...');

or with native fetch provided by next.js, it is easy to determine whether it is a successful api response

type User = { name: string };

const fetchLocalUser = async (args): Promise<User> => {
  const response = await fetch('/api/...', args);
  const result = (await response.json()) as ApiResponse<User>;

  if (!result?.success) {
    throw new Error(result.message);
  }

  // Here ts should be able to figure out it is SuccessApiResponse
  return result.data;
};

Compatibility

As next-api-handler provides a general interface to generate next.js handler, it will be easy to work with other libraries like following:

import { RouterBuilder } from 'next-api-handler'

const router = new RouterBuilder()

router.get( ... )
router.post( ... )

// here handler has type 'NextApiHandler' exposed from 'next'
const handler = router.build()

// ... work with wrapper or other plugins

A quick example with iron-session:

// pages/api/user.ts

import { withIronSessionApiRoute } from 'iron-session/next';
import { RouterBuilder } from 'next-api-handler';

const router = new RouterBuilder();
const handler = router.get((req) => req.session.user).build();

export default withIronSessionApiRoute(handler, {
  cookieName: 'myapp_cookiename',
  password: 'complex_password_at_least_32_characters_long',
  // secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
  cookieOptions: {
    secure: process.env.NODE_ENV === 'production',
  },
});

Reference

License

MIT

About

lightweight nextjs api handler wrapper, portable & configurable for serverless environment

Resources

License

Security policy

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 90.8%
  • JavaScript 4.8%
  • CSS 4.4%