Table of Contents
- Introduction
- Setting Up the Development Environment
- Creating the PostgreSQL Database
- Setting Up Prisma
- Creating Models and Migrations with Prisma
- Building the API Layer with Next.js API Routes
- Creating the Frontend with React and Next.js
- Connecting the Frontend with the Backend
- Handling Authentication with JWT
- Deploying the Full-Stack App
- Conclusion
1. Introduction
Building a full-stack application can be a rewarding and challenging experience. In this module, we’ll walk you through creating a complete full-stack app using Next.js, Prisma, and PostgreSQL. We will build a simple application that includes a backend for handling data, a frontend to display and interact with the data, and user authentication with JWT.
This app will allow you to perform CRUD operations (Create, Read, Update, Delete) on a database, and it will also cover authentication and secure handling of user data.
2. Setting Up the Development Environment
To get started, you will need the following tools installed on your machine:
- Node.js (LTS version recommended)
- PostgreSQL (local installation or use a cloud provider like Heroku or ElephantSQL)
- Prisma CLI (to interact with the database)
- Next.js (for the frontend and backend)
Installation of Dependencies:
- Install Next.js:
bashCopyEditnpx create-next-app@latest full-stack-app
cd full-stack-app
- Install Prisma and PostgreSQL:
bashCopyEditnpm install prisma @prisma/client
npm install pg
- Initialize Prisma:
bashCopyEditnpx prisma init
This will generate the prisma
folder and a schema.prisma
file.
3. Creating the PostgreSQL Database
If you’re using a local PostgreSQL instance, ensure PostgreSQL is running. If you’re using a hosted database, create a new PostgreSQL instance.
- Local Setup: If you’re using PostgreSQL locally, you can create a new database using the
psql
command or a GUI tool like pgAdmin.
bashCopyEditCREATE DATABASE full_stack_app;
- Heroku Setup: If you’re using Heroku, you can provision a PostgreSQL database via the Heroku dashboard or the CLI.
bashCopyEditheroku addons:create heroku-postgresql:hobby-dev
Once your database is set up, copy the connection URL to use in Prisma’s configuration.
4. Setting Up Prisma
Prisma is an ORM (Object Relational Mapping) tool that simplifies working with databases. We will configure Prisma to connect to the PostgreSQL database and create models.
Configure Database URL in .env
File:
Edit the .env
file generated by Prisma and add your PostgreSQL connection string.
envCopyEditDATABASE_URL="postgresql://user:password@localhost:5432/full_stack_app?schema=public"
Initialize Prisma with Schema:
In the schema.prisma
file, define the database schema. For this tutorial, we’ll create a simple User
model.
prismaCopyEdit// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
username String @unique
email String @unique
password String
createdAt DateTime @default(now())
}
Run Prisma migration to create the database schema:
bashCopyEditnpx prisma migrate dev --name init
5. Creating Models and Migrations with Prisma
Now that we’ve defined the User
model, you can run the Prisma migration to create the necessary tables in your database.
bashCopyEditnpx prisma migrate dev --name create-user-table
You can verify the table is created by accessing the PostgreSQL database or using Prisma Studio:
bashCopyEditnpx prisma studio
6. Building the API Layer with Next.js API Routes
Next.js provides API routes, allowing you to create a backend directly within your Next.js app. API routes are stored in the pages/api
directory.
We’ll create an API route to handle user registration.
Create Registration API Route:
tsxCopyEdit// pages/api/register.ts
import { NextApiRequest, NextApiResponse } from "next";
import bcrypt from "bcryptjs";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") {
const { username, email, password } = req.body;
// Hash the password
const hashedPassword = await bcrypt.hash(password, 10);
// Create a new user in the database
try {
const user = await prisma.user.create({
data: {
username,
email,
password: hashedPassword,
},
});
res.status(201).json(user);
} catch (error) {
res.status(400).json({ error: "Error creating user" });
}
} else {
res.status(405).json({ error: "Method Not Allowed" });
}
}
In this route:
- We accept the
username
,email
, andpassword
in thePOST
request. - The password is hashed using bcryptjs before storing it in the database.
- If the user is created successfully, we return the user data as a JSON response.
7. Creating the Frontend with React and Next.js
The frontend will allow users to register and view the list of registered users.
Create the Registration Form:
tsxCopyEdit// pages/register.tsx
import { useState } from "react";
import axios from "axios";
const Register = () => {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await axios.post("/api/register", { username, email, password });
alert("Registration successful!");
} catch (error) {
setError("An error occurred. Please try again.");
}
};
return (
<div>
<h1>Register</h1>
{error && <p>{error}</p>}
<form onSubmit={handleSubmit}>
<label>
Username:
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</label>
<label>
Email:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</label>
<label>
Password:
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</label>
<button type="submit">Register</button>
</form>
</div>
);
};
export default Register;
In this form:
- The user enters their username, email, and password.
- Upon form submission, an API request is made to the
/api/register
route to create a new user.
8. Connecting the Frontend with the Backend
With the frontend and backend set up, users can register by sending a POST request to the /api/register
API route.
Make sure that the frontend and backend are connected and data is being correctly passed between them.
9. Handling Authentication with JWT
For user authentication, we can use JWT (JSON Web Tokens). After successful login or registration, we’ll issue a JWT to the user, which can be used for subsequent requests to access protected routes.
Create an Auth API Route:
tsxCopyEdit// pages/api/login.ts
import { NextApiRequest, NextApiResponse } from "next";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") {
const { email, password } = req.body;
// Check if user exists
const user = await prisma.user.findUnique({ where: { email } });
if (!user) {
return res.status(401).json({ error: "Invalid credentials" });
}
// Verify password
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return res.status(401).json({ error: "Invalid credentials" });
}
// Create JWT
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET!, { expiresIn: "1h" });
res.status(200).json({ token });
} else {
res.status(405).json({ error: "Method Not Allowed" });
}
}
10. Deploying the Full-Stack App
Once the app is ready, you can deploy it to services like Vercel, Netlify, or Heroku. These platforms support Next.js and PostgreSQL apps with minimal configuration.
- For Vercel and Netlify, deploy your app by connecting your GitHub repository to the platform and configuring environment variables for your database and JWT secret.
11. Conclusion
In this module, we built a full-stack application using Next.js, Prisma, and PostgreSQL. We set up the backend with Next.js API routes, connected to a PostgreSQL database using Prisma, and built a simple user registration system. We also added authentication with JWT and deployed the application to production.
This approach to building full-stack apps with Next.js and Prisma makes it easier to manage your database, handle API requests, and build scalable applications with minimal setup.