DEV Community

Cover image for Protecting Routes in Next.js with Unkey: A Personal Experience
Istaprasad Patra
Istaprasad Patra

Posted on

Protecting Routes in Next.js with Unkey: A Personal Experience

When we build web applications with Next.js, securing sensitive routes is a critical aspect of maintaining the integrity of the app and user data. I recently implemented Unkey, a modern security platform, to protect the routes in a Next.js project.
Here’s a detailed walkthrough of my experience with it, highlighting why I choose Unkey, how I integrated it, and the results I achieved.

About Unkey

Before diving into the integration, I was looking for a solution that could offer robust access control with minimal configuration. Unkey stood out because of its:

  • Simplicity: It provides pre-built SDKs for JavaScript frameworks, making it easy to set up.
  • Granular Control: Unkey enables fine-grained access control, letting you protect routes based on user roles, permissions, and other custom rules.
  • Scalability: It works well for both small projects and large-scale applications, which was appealing for future growth.

Setting up Unkey into NextJs

I am building a stock management application and in this blog I will be telling you about the backend of this project.

The steps to setup are pretty clearly given in the docs. Firstly, we need create an account in unkey.com and create an api key and then go through the below steps.

 npx create-next-app@latest
 npm install @unkey/nextjs
 npm install mongoose mongodb
Enter fullscreen mode Exit fullscreen mode

Setting up Database

I am using mongodb as my database, you can database as per your comfort.

import { NextResponse } from "next/server";
import mongoose, { Document, Model, Schema } from "mongoose";

const uri: string = process.env.MONGO_URI as string;

// Connect to MongoDB using mongoose
export async function connectToDatabase(): Promise<void> {
  try {
    await mongoose.connect(uri, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    console.log("Connected to the database");
  } catch (error) {
    console.error("Database connection error:", error);
    throw error;
  }
}

// Define the Inventory interface
interface IInventory extends Document {
  slug: string;
  quantity: number;
  price: string;
}

// Define the schema
const inventorySchema: Schema<IInventory> = new Schema({
  slug: {
    type: String,
    required: true,
    unique: true,
  },
  quantity: {
    type: Number,
    required: true,
  },
  price: {
    type: String,
    required: true,
  },
});

// Create the model
const Inventory: Model<IInventory> = mongoose.models.Inventory || mongoose.model<IInventory>("Inventory", inventorySchema);

Enter fullscreen mode Exit fullscreen mode

Making Fetching Data, Adding Data and Searching Data Routes

import { NextResponse } from "next/server";
import { withUnkey } from "@unkey/sdk/next";  // Ensure this import path is correct based on your SDK setup.
import type { NextRequest } from "next/server";

export const POST = withUnkey(async (req: NextRequest) => {
  if (!req.unkey?.valid) {
    return new NextResponse("Unauthorized", { status: 403 });
  }

  await connectToDatabase();

  try {
    const data = await req.json();
    console.log(data);
    const product = await Inventory.create(data);
    return NextResponse.json({ product, ok: true }, { status: 200 });
  } catch (error: any) {
    return NextResponse.json({ error: error.message }, { status: 400 });
  }
});

export const GET = withUnkey(async (req: NextRequest) => {
  if (!req.unkey?.valid) {
    return new NextResponse("Unauthorized", { status: 403 });
  }

  await connectToDatabase();

  try {
    const allProducts = await Inventory.find({});
    return NextResponse.json({ allProducts }, { status: 200 });
  } catch (error: any) {
    return NextResponse.json({ error: error.message || "An error occurred" }, { status: 400 });
  }
});

Enter fullscreen mode Exit fullscreen mode

Now the searching data route is in another page :

import { NextResponse } from "next/server";
import { withUnkey } from "@unkey/sdk/next"; 
import { connectToDatabase } from "../mongo/route"; 
import { Inventory } from "../mongo/route";
import type { NextRequest } from "next/server";

export const GET = withUnkey(async (req: NextRequest) => {
  if (!req.unkey?.valid) {
    return new NextResponse("Unauthorized", { status: 403 });
  }

  await connectToDatabase();

  try {
    const { searchParams } = new URL(req.url);
    const slug = searchParams.get("slug") || "";

    const allProducts = await Inventory.find({
      slug: { $regex: slug, $options: "i" },
    });

    return NextResponse.json({ allProducts }, { status: 200 });
  } catch (error: any) {
    return NextResponse.json({ error: error.message || "An error occurred" }, { status: 400 });
  }
});

Enter fullscreen mode Exit fullscreen mode

Benefits I Experienced

Using Unkey to secure my Next.js app brought several advantages:

  • Customization: I appreciated the flexibility to define custom rules for different user roles. This gave me full control over route access management.

  • Ease of Use: The SDK’s tight integration with Next.js allowed me to focus more on the business logic while handling security effortlessly.

Initially I faced a few problems as I wasn't able to understand what exactly it does but as I used it, I understood how it helps. In a big application, it will handle the security of the routes and remove a huge burden from my shoulders.

Conclusion

Using Unkey to protect routes in Next.js provided a powerful yet simple solution for managing access control in my application. Its integration with Next.js was seamless, and the flexibility it offers for defining custom rules based on user roles made it perfect for my needs.

For anyone looking for a scalable security solution to protect routes in their Next.js application, I highly recommend giving Unkey a try.

Top comments (0)