Module 8: Backend & API Integration in Next.js 14 with TypeScript

Module 8: Backend & API Integration in Next.js 14 with TypeScript

AI Reading

Quick summary of this article

This module teaches you how to build a full-stack backend with Next.js 14, covering database setup, API creation, and data handling. You will learn to create REST-style API routes in the App Router, connect Prisma ORM to databases like PostgreSQL or MongoDB, and integrate CRUD operations using either API calls from client components or Server Actions.

  • Set up a database with Prisma ORM, configure the schema, and run migrations to create models like User.
  • Create API endpoints under app/api/ for handling requests such as POST and GET, using Prisma to interact with the database.
  • Call these APIs from client components using fetch() to create and display data, like adding a new user or listing all users.
  • Optionally use Server Actions to directly call Prisma from server-side code, simplifying form handling without needing API routes.

Build Full-Stack APIs, Connect to Databases, and Handle CRUD Operations

Next.js 14 makes it easier than ever to create and consume backend APIs using the App Router. In this module, you’ll learn to:

  • Create REST-style API routes under app/api/
  • Connect Prisma ORM to PostgreSQL or MongoDB
  • Build and call API endpoints
  • Integrate CRUD logic into Server Actions or Client Components

🧱 Folder Structure

src/
├── app/
│   ├── api/
│   │   └── users/
│   │       └── route.ts      ← API endpoint (POST, GET)
│   ├── page.tsx
├── lib/
│   └── prisma.ts             ← Prisma client setup
├── server/
│   └── actions/              ← Server Actions
├── components/

✅ Step 1: Set Up Prisma with PostgreSQL or MongoDB

npm install prisma @prisma/client
npx prisma init

🔹 .env (PostgreSQL example)

DATABASE_URL="postgresql://user:password@localhost:5432/mydb"

🔹 prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id    Int    @id @default(autoincrement())
  name  String
  email String @unique
}

Run the migration:

npx prisma migrate dev --name init

✅ Step 2: Setup Prisma Client

// src/lib/prisma.ts
import { PrismaClient } from '@prisma/client';

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };

export const prisma =
  globalForPrisma.prisma || new PrismaClient({ log: ['query'] });

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;

✅ Step 3: Create an API Route – POST /api/users

// src/app/api/users/route.ts
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";

export async function POST(req: Request) {
  const body = await req.json();
  const { name, email } = body;

  try {
    const user = await prisma.user.create({ data: { name, email } });
    return NextResponse.json(user, { status: 201 });
  } catch (error) {
    return NextResponse.json({ error: "User already exists." }, { status: 400 });
  }
}

export async function GET() {
  const users = await prisma.user.findMany();
  return NextResponse.json(users);
}

✅ Step 4: Call API from Client Component (e.g. Create User)

"use client";
import { useState } from "react";

export default function UserForm() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const res = await fetch("/api/users", {
      method: "POST",
      body: JSON.stringify({ name, email }),
      headers: { "Content-Type": "application/json" },
    });

    if (res.ok) {
      alert("User created!");
    } else {
      alert("Something went wrong");
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4 max-w-md">
      <input
        className="border p-2 w-full"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
        required
      />
      <input
        className="border p-2 w-full"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        type="email"
        required
      />
      <button className="bg-blue-600 text-white px-4 py-2 rounded" type="submit">
        Create User
      </button>
    </form>
  );
}

✅ Step 5: Fetch Users from API

'use client';

import { useEffect, useState } from "react";

type User = { id: number; name: string; email: string };

export default function UserList() {
  const [users, setUsers] = useState<User[]>([]);

  useEffect(() => {
    fetch("/api/users")
      .then(res => res.json())
      .then(data => setUsers(data));
  }, []);

  return (
    <ul className="space-y-2 mt-6">
      {users.map(user => (
        <li key={user.id} className="border p-2 rounded">
          {user.name} ({user.email})
        </li>
      ))}
    </ul>
  );
}

🧠 Optional: Call Prisma Directly from Server Action

// src/server/actions/createUser.ts
'use server';

import { prisma } from "@/lib/prisma";

export async function createUser(formData: FormData) {
  const name = formData.get("name") as string;
  const email = formData.get("email") as string;

  return prisma.user.create({ data: { name, email } });
}

Then use in a form:

<form action={createUser}>
  <input name="name" />
  <input name="email" />
  <button type="submit">Submit</button>
</form>

✅ Summary

In this module, you learned how to:

Task Tool/Example
Setup a database Prisma + PostgreSQL or MongoDB
Create backend APIs app/api/route.ts (RESTful APIs)
Connect UI to backend fetch() from Client Component
Full CRUD logic Server Actions or API calls

🔜 Coming Up Next:

In Module 9, we’ll add Authentication & Authorization using next-auth, implement protected routes, and build a role-based system for admin vs user access.

Leave a Reply

Your email address will not be published. Required fields are marked *