codelynx.dev
🇫🇷🇬🇧

Back • 08/09/2024

The best way to create API Routes in NextJS App Directory

Written by Melvyn Malherbe on 08/09/2024


In NextJS it is possible to use the route.ts file to create routes... the problem is that we often end up writing the same code:

TS
const Schema = z.object({
  title: z.string();
})

export const POST = async (req: NextRequest) => {
  const json = await req.json();
  const body = Schema.parse(json);

  // ...
}

We also often:

  • Authenticate the user
  • Check the type of search params or params
  • ...

Now imagine a library that allows you to:

  1. TypeSafe all inputs automatically with Zod
  2. Use middleware
  3. Simplify routing considerably

That's what I created by contributing to the next-safe-route project and creating a fork next-zod-route!

Why did I create a fork? The maintainer of next-safe-route took a long time to respond to me and I wanted to create clean documentation, etc...

Installation and setup

You can install the library with npm:

BASH
npm install next-zod-route

Once done, we will create routes according to our needs in a file src/lib/safe-route.ts (I explain how I organize my files in this article)

Here is an example:

TS
export class RouteError extends Error {
  status?: number;
  constructor(message: string, status?: number) {
    super(message);
    this.status = status;
  }
}

export const route = createZodRoute({
  handleServerError: (e: Error) => {
    if (e instanceof RouteError) {
      return NextResponse.json(
        { message: e.message, status: e.status },
        {
          status: e.status,
        },
      );
    }

    return NextResponse.json({ message: "Internal server error" }, { status: 500 });
  },
});

We create a route with createZodRoute that handles what happens in case of an error.

Here you can add conditions for example for a Prisma error or others.

Once done, we will be able to use or even reuse route in a file /api/users/route.ts:

TS
import { route } from "~/lib/safe-route";

const BodySchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

export const POST = route
  .body(BodySchema)
  .handler(async (req, { body }) => {
    await prisma.user.create({
      data: {
        name: body.name,
        email: body.email,
      },
    })
  });

You can see that the handler has req (the NextJS request) and body. You can't see it here but all this is 100% TypeSafe:

Everything is 100% TypeSafe

middleware

What is interesting is to use middleware to reuse logic and handle error cases for our routes.

Imagine you want 2 middleware:

  1. The first one checks if the user is logged in
  2. The second one checks if the user is an admin

For that, we will be able to use route.use:

TS
export const route = createZodRoute({
  // Previous code
})

export const authRoute = route.use(async () => {
  const user = await auth();

  if (!user) {
    throw new RouteError("Session not found!");
  }

  return {
    user,
  };
});

export const adminRoute = authRoute.use(async (params) => {
  if (!params.context.user.isAdmin) {
    throw new RouteError("You are not admin!");
  }
});

Here we will be able to use two new route methods! With this system that allows composing and duplicating routes, we can create routes for all our needs.

It is then used exactly the same way as route:

TS
export const GET = authRoute.handler(async (req, params) => {
  // We have access to the context!
  return NextResponse.json(params.context.user);
});

As usual, everything is 100% TypeSafe which allows us to be confident in the functioning of our application.

If there is an error in the middleware, it will be handled by handleServerError and the execution of handler will never be performed.

Why use API Routes?

You might want to use next-safe-action which allows you to do server-actions without hassle.

But be careful!

Server Actions are not designed to fetch data.

You can see in this Tweet that server-actions have a problem, it is not easy to cache the return value.

If you really want to have client-side elements, using API Routes with TanStack Query is the best choice!

Conclusion

Feel free to tell me what you think of this library and if you plan to use it. In the meantime, you can sign up for my NextJS course where we understand everything about NextJS:

Le meilleur moyen d'apprendre NextJS !

Rejoins par développeurs, cette formation reçoit une note de 4.7 / 5 🚀

Reçois la formation gratuitement dans ta boîte mail :