In this blog post, I will walk you through the process of creating a full-stack bookstore application using NestJS for the backend and Next.js for the...
Ditulis Oleh zidan Pada 03 Apr 2024
In this blog post, I will walk you through the process of creating a full-stack bookstore application using NestJS for the backend and Next.js for the frontend.
The application will have features such as user login, adding books to a cart, purchasing books, and canceling book orders.
Live Frontend Url
The website is live in : `https://frontend-bookstore-nextjs.vercel.app/`
Github : https://github.com/muhzulzidan/frontend-bookstore
Live Backend Url
The API is live in : `https://bookstore-nestjs.vercel.app/`
Github : https://github.com/muhzulzidan/bookstore
Introduction
The bookstore application is a simple yet functional online store where users can browse through a collection of books, add them to a cart, and proceed to purchase them. The application is built using NestJS, a progressive Node.js framework for building efficient and scalable server-side applications, and Next.js, a React framework for building JavaScript applications.
Backend with NestJS, Prisma, and Supabase
The backend of our application is built using NestJS, Prisma, and Supabase. We chose NestJS because it provides an out-of-the-box application architecture which allows for effortless creation of highly testable, scalable, loosely coupled, and easily maintainable applications.
Prisma is an open-source database toolkit. It includes an ORM, a database schema migration tool, and a database proxy for reading and writing data in your database in a secure manner. We use Prisma to interact with our database, handle database migrations, and generate TypeScript types based on our database schema.
Supabase is an open-source Firebase alternative. It provides a set of tools for building backend applications, including a Postgres database, authentication and authorization, real-time subscriptions, and storage. We use Supabase as our database solution.
Our backend features include:
User authentication: Users can log in to the application. We use Supabase's authentication and authorization features to handle user registration, login, and session management.
Book management: The application handles operations related to books such as fetching book details. We use Prisma to interact with our Supabase database and perform CRUD operations on the books table.
Cart management: Users can add books to a cart. We use Prisma to interact with our Supabase database and perform CRUD operations on the cart table.
Order management: Users can place orders and also have the option to cancel them. We use Prisma to interact with our Supabase database and perform CRUD operations on the orders table.
The backend is structured following the controller-service-repository pattern. The controller handles HTTP requests and responses, the service contains the business logic, and the repository interacts with the database using Prisma.
Database Schema with Prisma
Our application's data is stored in a PostgreSQL database. We use Prisma to define and interact with our database schema. Prisma provides a high-level, type-safe way of defining our database schema and performing database operations.
Here's our schema.prisma file, which defines our database schema:
```
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
model Book {
id Int @id @default(autoincrement())
title String
writer String
coverImage String
price Float
tags String[]
orders Order[]
}
model User {
id Int @id @default(autoincrement())
username String @unique
password String
}
model Order {
id Int @id @default(autoincrement())
customerId Int
bookId Int
book Book @relation(fields: [bookId], references: [id])
customer Customer @relation(fields: [customerId], references: [id])
}
model Customer {
id Int @id @default(autoincrement())
name String
points Int
userId Int @unique
orders Order[]
}
```
In the above schema:
The Book model represents a book in our bookstore. It has fields like id, title, writer, coverImage, price, and tags.
The User model represents a user of our application. It has fields like id, username, and password.
The Order model represents an order placed by a customer. It has fields like id, customerId, and bookId. It also has relations to the Book and Customer models.
The Customer model represents a customer who can place orders. It has fields like id, name, points, and userId. It also has a relation to the Order model.
This schema allows us to store and manage all the data needed for our bookstore application. With Prisma, we can easily perform database operations like fetching a book, creating an order, or updating a customer's points.
Our backend features include:
User authentication: Users can log in to the application.
Book management: The application handles operations related to books such as fetching book details.
Cart management: Users can add books to a cart.
Order management: Users can place orders and also have the option to cancel them.
The backend is structured following the controller-service-repository pattern. The controller handles HTTP requests and responses, the service contains the business logic, and the repository interacts with the database.
Frontend with Next.js
The frontend of our application is built using Next.js and styled with Tailwind CSS. Next.js allows us to build server-side rendered React applications with ease. It also provides features like static exporting and CSS-in-JS.
Tailwind CSS is a utility-first CSS framework that is highly customizable and works very well with modern JavaScript frameworks like React. It allows us to build responsive designs with ease.
Our frontend features include:
User interface: Users can view a list of books, details of a book, and their cart. The user interface is built using React and styled with Tailwind CSS. We also use the Shadcn UI library for some UI components like dialogs, dropdown menus, and toasts.
State management: We use Redux for managing the state of our application. This includes the current logged-in user, the books in the user's cart, etc. We use the @reduxjs/toolkit package to simplify Redux setup and usage, and next-redux-wrapper to integrate Redux with our Next.js application.
Responsive design: The design of the application is responsive and works well on all devices. We use Tailwind CSS for styling, which makes it easy to build responsive designs.
Here's a snippet from our package.json file that shows some of the key dependencies for our frontend:
```
{
"dependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-toast": "^1.1.5",
"@reduxjs/toolkit": "^2.2.2",
"next": "14.1.4",
"next-redux-wrapper": "^8.1.0",
"react": "^18",
"react-dom": "^18",
"react-redux": "^9.1.0",
"redux": "^5.0.1",
"tailwindcss": "^3.3.0"
},
"devDependencies": {
"autoprefixer": "^10.0.1",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}
```
In the next part of this blog post, we'll dive deeper into how we implemented some of these features in our Next.js and Tailwind CSS frontend. Stay tuned!
Conclusion
Building a full-stack application with NestJS and Next.js is a great way to create scalable and efficient web applications. The combination of NestJS's scalable architecture and Next.js's capabilities for server-side rendering makes it a powerful stack for building web applications.
In this post, we walked through the process of creating a simple bookstore application. However, the principles we discussed can be applied to any type of web application. Whether you're building a small side project or a large-scale production application, NestJS and Next.js are excellent choices for your tech stack.