DEV Community

t sriya
t sriya

Posted on

Part 1 : Building an Authentication System from Scratch – Backend Setup

Setting up a scalable authentication backend using Node.js, Express.js, PostgreSQL, and a layered architecture.

Introduction
__

Authentication is one of the most essential features of any modern web application. While many projects rely on third-party providers such as Firebase Auth, Auth0, or Clerk, I wanted to understand what actually happens behind the scenes when a user registers, logs in, resets their password, or logs out.

To achieve that, I decided to build a complete authentication system from scratch.

In this project, I implemented the entire authentication flow using:

React
Node.js
Express.js
PostgreSQL
JWT
Redux Toolkit
Tailwind CSS
Resend

The goal wasn't just to make authentication work—it was to understand the architecture, security considerations, and communication between the frontend, backend, and database.

This article is the first part of the series, where we'll build the backend foundation that powers the entire authentication system.

Project Structure

I prefer keeping both the frontend and backend inside a single project repository. This makes development, version control, and deployment much easier to manage.

Auth-Flow

├── auth-backend

└── auth-frontend

Inside the backend, I initialized a new Node.js application.

mkdir auth-project
cd auth-project

mkdir auth-backend
mkdir auth-frontend

cd auth-backend

mkdir server
cd server

npm init -y
Installing Dependencies

Next, I installed the packages required for building the authentication APIs.

npm install express pg bcrypt dotenv cors
npm install -D nodemon

Each package serves a specific purpose.

Package Purpose
Express Build REST APIs
pg Connect Node.js with PostgreSQL
bcrypt Securely hash passwords
dotenv Manage environment variables
cors Enable frontend-backend communication
nodemon Automatically restart the server during development

Instead of installing unnecessary libraries upfront, I only installed the packages required for the current stage of development. Additional libraries such as JWT and Resend will be introduced later as the project evolves.

Organizing the Backend

As applications grow, keeping everything inside a single file quickly becomes difficult to maintain.

To keep the project modular and scalable, I organized the backend into multiple layers.

server

├── src

│── controllers
│── services
│── repositories
│── middleware
│── routes
│── db
│── utils

├── server.js
└── package.json

Each folder has a clearly defined responsibility.

Controllers handle incoming HTTP requests and responses.
Services contain business logic.
Repositories interact with PostgreSQL.
Routes define API endpoints.
Middleware contains reusable request-processing logic.
DB manages database connections.
Utils stores helper functions.

This layered architecture keeps responsibilities separated and makes the codebase much easier to maintain and extend.

Setting Up PostgreSQL

Since user information needs to persist across sessions, I chose PostgreSQL as the database.

First, create the database.

CREATE DATABASE auth_project;

Then create the users table.

CREATE TABLE users (
   id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
   username VARCHAR(200) NOT NULL,
   email VARCHAR(250) NOT NULL UNIQUE,
   password_hash VARCHAR NOT NULL,
   created_at TIMESTAMP DEFAULT NOW()
);
Enter fullscreen mode Exit fullscreen mode

Notice that the table stores password_hash instead of the user's original password. This is one of the most important security practices when building authentication systems.

Environment Variables

Sensitive information such as database credentials should never be hardcoded inside the application.

Instead, I created a .env file.

PORT=5000

DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=your_password
DB_NAME=auth_project

Enter fullscreen mode Exit fullscreen mode

The .env file is excluded from version control.

node_modules
.env

This prevents sensitive credentials from being pushed to GitHub.

Creating the Database Connection

Instead of creating a new PostgreSQL connection inside every repository, I created a reusable database connection using pg.Pool.

const { Pool } = require("pg");

const pool = new Pool({
  host: process.env.DB_HOST,
  port: process.env.DB_PORT,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
});

module.exports = pool;
Enter fullscreen mode Exit fullscreen mode

This single connection pool is shared throughout the application, improving performance and reducing resource usage.

Creating the Express Application

Next, I configured the Express server.

const express = require("express");
const cors = require("cors");

const app = express();

app.use(express.json());

app.use(
  cors({
    origin: "http://localhost:5173",
    credentials: true,
  })
);

module.exports = app;

Enter fullscreen mode Exit fullscreen mode

At this stage, the server is capable of:

Parsing JSON request bodies
Accepting requests from the React frontend
Preparing for future authentication middleware
Server Entry Point

The application starts from server.js.

require("dotenv").config();

const app = require("./src/app");

const PORT = process.env.PORT || 5000;

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Running

npm run dev

starts the backend server.

Server running on port 5000
Verifying the Backend

Before implementing authentication, I always verify that the server is functioning correctly.

I added a simple health-check endpoint.

router.get("/", (req, res) => {
  res.json({
    status: "ok",
  });
});
Enter fullscreen mode Exit fullscreen mode

Request:

GET /health

Response:

{
"status": "ok"
}

This confirms that the backend is configured correctly before moving on to authentication.

Why I Chose a Layered Architecture

One mistake I often see in beginner projects is placing SQL queries directly inside controllers.

Instead, I separated responsibilities into three layers.

Controller


Service


Repository


PostgreSQL
Controller

Receives the HTTP request and returns the HTTP response.

Service

Contains all business logic, including:

Email validation
Password hashing
JWT generation
Authentication rules
Repository

Handles database operations only.

This separation makes the project easier to:

Debug
Test
Scale
Maintain

As more authentication features are added, each layer continues to have a single responsibility.

Conclusion

With the backend infrastructure now in place, the application is ready to implement authentication features.

In the next article, we'll build the User Registration Flow, where we'll:

Validate user input
Prevent duplicate accounts
Securely hash passwords using bcrypt
Store users in PostgreSQL
Return a safe API response

Continue Reading Part 2: https://dev.to/t_sriya_2af6abc7e8d4e87da/part-2building-an-authentication-system-from-scratch-backend-setup-59gg

Live App: https://auth-flow-five-iota.vercel.app/auth/
Backend API: https://auth-flow-backend-1v2h.onrender.com/
github url: https://github.com/sriyaT/Auth-Flow

Connect : LinkedIn : https://www.linkedin.com/in/t-sriya-b4234510a/, github : https://github.com/sriyaT

Author: Sriya T.

Top comments (0)