🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

How to Build a Full-Stack App with Next.js 15 and PostgreSQL in 2026

⏱️5 min read  ·  898 words



How to Build a Full-Stack App with Next.js 15 and PostgreSQL in 2026

TechPulse Editorial Team
Tech Writers · May 28, 2026
📅 May 28, 2026⏱ 16 min read📂 How-To🏷 nextjs · postgresql · fullstack · react

What We’re Building

A full-stack task management app: Next.js 15 App Router, PostgreSQL via Neon (serverless), Prisma ORM, Server Actions (no separate API routes for mutations), Auth.js v5 GitHub OAuth, deployed to Vercel.

Project Setup

npx create-next-app@latest taskapp \
  --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
cd taskapp
Next.js 15 key changes: Stable Turbopack (5× faster builds), React 19 required, async request APIs by default, fetch not cached by default, improved Server Actions.

Database: PostgreSQL with Neon + Prisma

Create a free database at neon.tech. Then:

npm install prisma @prisma/client
npx prisma init

In .env.local:

DATABASE_URL="postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/neondb?sslmode=require"
NEXTAUTH_SECRET="your-random-secret-here"
GITHUB_CLIENT_ID="your-github-oauth-app-id"
GITHUB_CLIENT_SECRET="your-github-oauth-app-secret"

Data Models

// prisma/schema.prisma
model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  tasks     Task[]
}

model Task {
  id        String     @id @default(cuid())
  title     String
  status    TaskStatus @default(PENDING)
  priority  Priority   @default(MEDIUM)
  userId    String
  user      User       @relation(fields: [userId], references: [id], onDelete: Cascade)
  createdAt DateTime   @default(now())
  updatedAt DateTime   @updatedAt

  @@index([userId])
}

enum TaskStatus { PENDING IN_PROGRESS COMPLETED CANCELLED }
enum Priority  { LOW MEDIUM HIGH URGENT }

npx prisma migrate dev --name init
npx prisma generate

// src/lib/db.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const db = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db;

Server Components & Data Fetching

App Router components are Server Components by default — they run on the server and can directly query the database:

// src/app/dashboard/page.tsx (Server Component)
import { db } from '@/lib/db';
import { auth } from '@/lib/auth';
import { redirect } from 'next/navigation';

export default async function DashboardPage() {
  const session = await auth();
  if (!session?.user) redirect('/');

  const tasks = await db.task.findMany({
    where: { userId: session.user.id, status: { not: 'CANCELLED' } },
    orderBy: [{ priority: 'desc' }, { createdAt: 'desc' }],
    take: 50,
  });

  return (
    <main className="container mx-auto p-6">
      <h1>My Tasks</h1>
      {tasks.map(task => <TaskCard key={task.id} task={task} />)}
    </main>
  );
}

No API route needed. No useEffect. No loading state boilerplate. The component fetches and renders in one shot.

Server Actions for Mutations

Server Actions replace API routes for form submissions and mutations:

// src/actions/tasks.ts
'use server';
import { db } from '@/lib/db';
import { auth } from '@/lib/auth';
import { revalidatePath } from 'next/cache';
import { z } from 'zod';

const Schema = z.object({
  title:    z.string().min(1).max(200),
  priority: z.enum(['LOW','MEDIUM','HIGH','URGENT']).default('MEDIUM'),
});

export async function createTask(formData: FormData) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  const parsed = Schema.safeParse({
    title:    formData.get('title'),
    priority: formData.get('priority') || 'MEDIUM',
  });
  if (!parsed.success) return { error: parsed.error.flatten().fieldErrors };

  await db.task.create({ data: { ...parsed.data, userId: session.user.id } });
  revalidatePath('/dashboard');
  return { success: true };
}

The revalidatePath call invalidates Next.js cache for that route, triggering a fresh server render with new data.

Authentication with Auth.js v5

npm install next-auth@beta @auth/prisma-adapter

// src/lib/auth.ts
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { db } from './db';

export const { auth, handlers, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(db),
  providers: [GitHub({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET! })],
  session: { strategy: 'database' },
  callbacks: { session: ({ session, user }) => ({ ...session, user: { ...session.user, id: user.id } }) }
});

Add the route handler: src/app/api/auth/[...nextauth]/route.ts → export handlers as GET, handlers as POST.

Deploying to Vercel

# Install Vercel CLI and deploy
npm install -g vercel
vercel

# Set environment variables
vercel env add DATABASE_URL
vercel env add NEXTAUTH_SECRET
vercel env add GITHUB_CLIENT_ID
vercel env add GITHUB_CLIENT_SECRET

vercel --prod

After deployment: update GitHub OAuth callback URL to https://your-app.vercel.app/api/auth/callback/github, then run npx prisma migrate deploy.

Production Checklist

  • Rate limiting — Upstash Ratelimit on Server Actions and API routes
  • Error boundaries — add error.tsx files for graceful fallbacks
  • Loading states — add loading.tsx for Suspense skeletons
  • Input validation — always use Zod on Server Actions
  • Connection pooling — add ?pgbouncer=true&connection_limit=1 to Neon URL
  • Security headers — add via next.config.ts headers()

🔧 Ready to Ship?

Next.js 15 + PostgreSQL is the dominant full-stack stack in 2026. Explore why TypeScript is essential for larger Next.js apps and master async/await patterns for Server Components and Actions.

Frequently Asked Questions

What is Next.js 15?

Latest Next.js: stable Turbopack (5× faster), React 19 required, async request APIs, improved Server Actions. App Router is the standard going forward.

App Router or Pages Router in 2026?

App Router for all new projects. Pages Router works but gets no new features.

What database to use with Next.js?

PostgreSQL via Neon (serverless) + Prisma or Drizzle ORM. Supabase if you need built-in auth and real-time.

What are Server Actions?

Async server functions (marked ‘use server’) called from React. Replace API routes for mutations. Type-safe, support optimistic updates.

How to deploy?

Vercel (zero config, made by Next.js team). Self-host: next build + next start behind nginx, or Docker.

✍️ Leave a Comment

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

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা