Back to Tutorials
Full-Stack
January 14, 2024

Build a Full-Stack App with Next.js 15

Complete tutorial on building a modern full-stack application with Next.js, Prisma, and PostgreSQL from scratch. Learn authentication, database design, API routes, and deployment.

M
Michael Chen
Full-Stack Engineer

In this comprehensive tutorial, you'll learn how to build a production-ready full-stack application using the latest technologies and best practices. By the end, you'll have a complete understanding of how to build, test, and deploy modern web applications.

Prerequisites

Before we begin, make sure you have the following ready:

  • Node.js 18+: Download from nodejs.org
  • Basic knowledge of React and TypeScript: You should be comfortable with components, hooks, and type definitions
  • PostgreSQL database: Either local installation or a cloud provider like Supabase or Neon
  • Text editor: VS Code recommended with ESLint and Prettier extensions
  • Git: For version control and deployment

Step 1: Project Setup

Let's start by creating a new Next.js project with all the modern tools configured. We'll use the official Next.js CLI which sets up everything we need.

Initialize the Project

Open your terminal and run:

npx create-next-app@latest my-fullstack-app --typescript --tailwind --app
cd my-fullstack-app
npm install

This command creates a new Next.js 15 project with TypeScript, Tailwind CSS, and the App Router already configured.

Project Structure

Your initial project structure should look like this:

my-fullstack-app/
├── app/
│   ├── layout.tsx
│   └── page.tsx
├── public/
├── package.json
├── tsconfig.json
└── tailwind.config.js

Step 2: Database Configuration

Set up Prisma as your ORM and connect it to PostgreSQL. Prisma provides type-safe database access and makes migrations easy.

Install Prisma

npm install prisma @prisma/client
npx prisma init

Configure Your Database

Update your .env file with your database connection string:

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

Create Your Schema

Edit prisma/schema.prisma to define your data model:

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String
  createdAt DateTime @default(now())
}

Run Migrations

npx prisma migrate dev --name init
npx prisma generate

Step 3: Authentication

Implement authentication using NextAuth.js with email/password and OAuth providers like Google and GitHub.

Install NextAuth

npm install next-auth @auth/prisma-adapter
npm install bcryptjs
npm install -D @types/bcryptjs

Configure NextAuth

Create app/api/auth/[...nextauth]/route.ts:

import NextAuth from "next-auth"
import GithubProvider from "next-auth/providers/github"
import GoogleProvider from "next-auth/providers/google"

export const authOptions = {
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    GoogleProvider({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
    }),
  ],
}

const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }

Step 4: API Routes

Create API routes for CRUD operations. We'll use Server Actions for mutations and API routes for queries, following Next.js 15 best practices.

Create a Server Action

Create app/actions/posts.ts:

'use server'

import { prisma } from '@/lib/prisma'
import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string
  
  await prisma.post.create({
    data: { title, content, authorId: 'user-id' }
  })
  
  revalidatePath('/posts')
}

Step 5: Frontend Components

Build reusable React components with proper TypeScript types and responsive design. We'll create components for displaying and creating posts.

Post List Component

import { prisma } from '@/lib/prisma'

export default async function PostList() {
  const posts = await prisma.post.findMany({
    include: { author: true },
    orderBy: { createdAt: 'desc' }
  })
  
  return (
    
{posts.map(post => (

{post.title}

{post.content}

By {post.author.name}

))}
) }

Step 6: Deployment

Deploy your application to Vercel with automatic CI/CD, environment variables, and database migrations.

Push to GitHub

git init
git add .
git commit -m "Initial commit"
git remote add origin your-repo-url
git push -u origin main

Deploy to Vercel

  • Go to vercel.com and sign in
  • Click "New Project"
  • Import your GitHub repository
  • Add environment variables (DATABASE_URL, NEXTAUTH_SECRET, etc.)
  • Click "Deploy"

Testing

Add tests to ensure your application works correctly:

npm install -D @testing-library/react @testing-library/jest-dom jest
npm install -D @types/jest

Next Steps

You now have a fully functional full-stack application! Here are some ideas to expand your project:

  • Real-time updates: Add WebSocket support for live updates
  • File uploads: Integrate cloud storage like AWS S3 or Cloudinary
  • Advanced search: Implement full-text search with Postgres or Algolia
  • Email notifications: Send emails using SendGrid or Resend
  • Payment processing: Integrate Stripe for subscriptions

Conclusion

Congratulations! You've built a production-ready full-stack application with modern best practices. Continue learning by adding features, optimizing performance, and exploring advanced patterns like caching, background jobs, and microservices.

Topics:
Continue Reading

Completed this tutorial?

Explore more step-by-step guides to level up your skills and master modern web development

Weekly Updates
New articles every week
Expert Insights
In-depth tutorials
View More Tutorials