R
Rishtaara
Next.js: Zero to Full-Stack
Lesson 32 of 42Article19 min

Dynamic API Routes & MongoDB Integration

Use [id] folders for dynamic segments. In Route Handlers, read params from the second argument — in recent Next.js, params is a Promise you must await.

Dynamic route handlers

Use [id] folders for dynamic segments. In Route Handlers, read params from the second argument — in recent Next.js, params is a Promise you must await.

Real-life example: /api/courses/42 is like asking for locker number 42 — the [id] folder is the locker number slot.

Tip: Validate that id looks like a MongoDB ObjectId (24 hex chars) before querying — bad ids should return 400, not crash the driver.
app/api/courses/[id]/route.ts
import { NextRequest, NextResponse } from "next/server";

type Ctx = { params: Promise<{ id: string }> };

export async function GET(_req: NextRequest, context: Ctx) {
  const { id } = await context.params;
  // fetch one course by id...
  return NextResponse.json({ id, title: "Sample course" });
}

export async function DELETE(_req: NextRequest, context: Ctx) {
  const { id } = await context.params;
  // delete course...
  return NextResponse.json({ deleted: id });
}

MongoDB connect example

Reuse one connection across hot reloads in development. Store MONGODB_URI in .env.local. Below uses the native MongoDB driver; Mongoose works the same idea with mongoose.connect.

Real-life example: Caching the Mongo client is keeping one taxi running for the whole office day, not calling a new cab for every short errand.

lib/mongodb.ts — cached connection
import { MongoClient } from "mongodb";

const uri = process.env.MONGODB_URI;
if (!uri) throw new Error("Missing MONGODB_URI");

declare global {
  // eslint-disable-next-line no-var
  var _mongoClientPromise: Promise<MongoClient> | undefined;
}

const client = new MongoClient(uri);
const clientPromise =
  global._mongoClientPromise ?? client.connect();

if (process.env.NODE_ENV !== "production") {
  global._mongoClientPromise = clientPromise;
}

export default clientPromise;
Mongoose alternative (sketch)
import mongoose from "mongoose";

export async function dbConnect() {
  if (mongoose.connection.readyState >= 1) return;
  await mongoose.connect(process.env.MONGODB_URI!);
}

CRUD sketch

Create, Read, Update, Delete against a courses collection. Keep ObjectId parsing and error handling tidy.

Real-life example: CRUD is managing a notebook — add a page, read it, edit a line, tear a page out.

Tip: Never expose your MongoDB URI to the browser. Only talk to Mongo from Server Components, Server Actions, or Route Handlers.
CRUD helpers sketch
import { ObjectId } from "mongodb";
import clientPromise from "@/lib/mongodb";

async function courses() {
  const client = await clientPromise;
  return client.db("knowvora").collection("courses");
}

export async function listCourses() {
  return (await courses()).find({}).toArray();
}

export async function createCourse(title: string) {
  const result = await (await courses()).insertOne({
    title,
    createdAt: new Date(),
  });
  return result.insertedId;
}

export async function updateCourse(id: string, title: string) {
  await (await courses()).updateOne(
    { _id: new ObjectId(id) },
    { $set: { title, updatedAt: new Date() } }
  );
}

export async function deleteCourse(id: string) {
  await (await courses()).deleteOne({ _id: new ObjectId(id) });
}