next-tapi
TypeScript icon, indicating that this package has built-in type declarations

1.1.1 • Public • Published

Next tAPI

Next tAPI (Next.js typesafe API) is an intuitive and powerful abstraction of Next.js API Routes. Taking inspiration from tRPC, Next tAPI is DX focussed, making the development of your Next.js API easier, safer, and more enjoyable.

Features:

  • fully typed
  • middleware with type inference
  • simple error handling
  • enforce response type consistency

Contents

Installation

npm install next-tapi       # npm
yarn add next-tapi          # yarn
pnpm add next-tapi          # pnpm

Getting Started

Define API method handlers on the router object and export.

// pages/api/some-route
import { createRouter } from "next-tapi";
const router = createRouter();

router.get(({ res, res, ...}) => { ... });
router.post(({ res, res, ...}) => { ... });
router.delete(({ res, res, ...}) => { ... });
router.put(({ res, res, ...}) => { ... });
router.patch(({ res, res, ...}) => { ... });

export default router.export();

The object returned by each method handler will be sent. The status code can be changed as usual on the res object.

router.get(({ res }) => {
    // create some resource
    res.status(201);
    return { ... }
})

Defining response type

It may be useful to define a return type which can be done by defining the generic function.

router.get<{ id: number }>(() => {
  return {
    id: "123", // error, must be a number
  };
});

Error handling

Errors thrown in an API method handler or middleware will be caught and processed. A custom error handler can be easily added to handle custom errors. Next tAPI provides its own TapiError which is handled with the default error handler.

import { TapiError } from "next-tapi";

router.get((req) => {
  throw new TapiError({ status: 400, message: "Oh no!" });
  return { ... };
});

Middleware

Middleware in Next tAPI is similar to Express but typed variables are accessible in subsequent middleware and method handlers in the fields object.

Note that Next tAPI middleware does not a replacement for Next.js middleware. Next tAPI middleware is especially useful when typed variables want to be passed to a method handler.

router
  .middleware(async ({ req, res, next }) => {
    const session = await getSession(req, res);
    return next({
      session,
    });
  })
  .middleware(({ fields, next }) => {
    fields.session; // fully typed
    return next();
  })
  .get(({ fields }) => {
    fields.session; // fully typed
    return { ... };
  });

Error handling in middleware

Errors thrown in middleware will be handled as normal by the error handler.

router.middleware(async ({ req, res, next }) => {
  throw new TapiError({ status: 400, message: "Oh no!" });
  return next();
});

Reusing middleware

Reusable middleware can be created with the createMiddleware function.

export const authMiddleware = createMiddleware(asyncc ({ req, res, next }) => {
  const session = await getSession(req, res);
  if (!session) throw new TapiError({ status: 403, "Not allowed!" });

  return next({
    session
  });
});

The middleware can be easily consumed.

import { authMiddleware } from "path/to/middleware"

router.middleware(authMiddleware).get(({ fields }) => {
    fields.session // typed
    return { ... }
})

Special middleware

Before defining the method handler, the body and query methods can be defined which validate the request body and query respectively, which are then available in the method handler. Next tAPI is agnostic to choice of validation; however, this can be done easily with zod, yup, or other validation libraries.

Zod

router
  .body(zodBodySchema.parse)
  .query(zodQuerySchema.parse)
  .get(({ body }) => {
    // body and query typesafe
    return { ... };
  })

Yup

router
  .body((body) => yupBodySchema.cast(body))
  .query((query) => yupQuerySchema.cast(query))
  .post(({ body }) => {
    // body and query typesafe
    return { ... };
  });

Reusable query and body resolvers can be created with the createQueryResolver and createResolver functions.

Customisation

Enforcing response types

Responses returned by method handlers can be forced to extend a certain shape. Let's say we wanted all successful responses to extend the following interface:

interface StandardSuccessResponse<T extends {}> {
  success: true;
  data: T;
}

We could do so using the generic:

const router = createRouter<StandardSuccessResponse<{}, StandardErrorResponse>>();

router.get(() => {
    return {
        user: { ... } // error, does not extend StandardSuccessResponse<{}>
    }
}

Response types defined must also extend the specified type.

router.get<StandardResponse<{ name: string }>>(() => {
    return {
        success: true,
        data: { name: "Next tAPI" }
    }
}

Custom error handler

A custom error handler can easily be added to handle custom errors or to change the error response.

interface StandardErrorResponse {
  success: false;
  error: { message: string };
}

const myCustomErrorHandler: ErrorHandler<StandardErrorResponse> = (
  req,
  res,
  err
) => {
  // handle error
  return res.status(400).json({
    success: false,
    error: {
      message: "Oh no!",
    },
  });
};

const router = createRouter<StandardSuccessResponse<{}>, StandardErrorResponse>(
  {
    errorHandler: myCustomErrorHandler,
  }
);

Router types

It may be convenient to define a few types of routers with different middleware.

import { authMiddleware, logRequest } from "path/to/middleware";
import { myCustomErrorHandler } from "path/to/myCustomErrorHandler";
import { createRouter } from "next-tapi";

export const mainRouter = () => {
  return createRouter<StandardSuccessResponse<{}>>({
    errorHandler: myCustomErrorHandler,
  }).middleware(logRequest);
};

export const authRouter = () => {
  return createRouter<StandardSuccessResponse<{}>>({
    errorHandler: myCustomErrorHandler,
  })
    .middleware(logRequest)
    .middleware(authMiddleware);
};

Comparison

Method handlers

Regular Next.js

Without an abstraction like Next tAPI, API routes get extremely repetitive.

const handler: NextApiHandler = (req, res) => {
  switch (req.method) {
    case "GET":
      return handleGET(req, res);
    case "POST":
      return handlePOST(req, res);
    default:
      return res.status(405).json({
        error: `${req.method} method not supported.`,
      });
  }
};

export default handler;

Next tAPI

Forget the switch-case, or other unideal solutions. Define the methods you want to support and the error handler will take care of the rest.

import { createRouter } from "next-tapi";
const router = createRouter();

router.get(() => {
    return { ... }
})

router.post(() => {
    return { ... }
})

export default router.export();

Error handling

Regular Next.js

Sending errors (especially in a consistent way) is especially annoying in regular API routes.

const handler: NextApiHandler = async (req, res) => {
  if (req.method !== "GET")
    return res.status(405).json({
      error: `${req.method} method not supported.`,
    });

  const session = await getSession(req, res);

  if (!session)
    return res.status(401).json({
      error: `You must be authenticated.`,
    });

  if (!session.role === "ADMIN")
    return res.status(403).json({
      error: `You are not authorised to access this resource.`,
    });

  try {
    const data = await db.query("some_table").where({
      user_id: session.user.id,
    });

    if (!data)
      return res.status(404).json({
        error: "Resource not found",
      });
    return res.status(200).json({ data });
  } catch (err) {
    return res.status(500).json({
      error: "Some DB error.",
    });
  }
};

Next tAPI

Simply throw errors in the handler body and consistently handle errors in the error handler. This is similar to Express.

router.middleware(authMiddleware).get(async ({ fields }) => {
  const data = await db.query("some_table").where({
    user_id: session.user.id,
  });
  if (!data) throw new TapiError({ status: 404, message: "Oh no!" });
  return { data };
});

Coming soon

  • end-to-end type safety

Package Sidebar

Install

npm i next-tapi

Weekly Downloads

1

Version

1.1.1

License

MIT

Unpacked Size

30.3 kB

Total Files

25

Last publish

Collaborators

  • reubendrummond