Building a Secure Login and Signup API with Node.js, PostgreSQL, and Modern Tools
When it comes to building a scalable API, implementing secure login and signup functionality is one of the most critical steps. Here’s a detailed walkthrough of how I set up a scalable and secure API using Node.js, PostgreSQL, bcrypt, and more. Along the way, I’ll share the exact steps, a few fun tidbits, and plenty of status codes to keep things professional (and functional). Let’s dive in! π
1. Setting Up the Project
Every great API starts with a solid foundation. Here’s how I got started:
Step 1.1: Initialize the Project
-
Create a new directory for the project:
mkdir login-signup-api && cd login-signup-api -
Initialize a new Node.js project:
npm init -yThis generates a
package.jsonfile with default settings. -
Install the required packages:
npm install express pg bcrypt dotenv cors body-parser- express: For building the API.
- pg: PostgreSQL client for Node.js.
- bcrypt: For password hashing.
- dotenv: To manage environment variables securely.
- cors: To handle cross-origin requests.
- body-parser: To parse incoming JSON requests.
Step 1.2: Organize the File Structure
Here’s the structure I used to keep everything neat and scalable:
login-signup-api/
|-- controllers/
| |-- authController.js
|-- database/
| |-- db.js
|-- middleware/
| |-- authMiddleware.js
|-- routes/
| |-- authRoutes.js
|-- .env
|-- app.js
|-- package.json
- controllers/: Contains the logic for handling routes.
- database/: Handles database connections.
- middleware/: Custom middleware for authentication.
- routes/: Defines the API routes.
2. Connecting to PostgreSQL
Before we dive into authentication, we need to set up the database.
Step 2.1: Install PostgreSQL (if not already installed)
Follow the instructions for your operating system to install PostgreSQL. Once installed, create a new database:
createdb login_signup_db
Step 2.2: Configure the Database Connection
Create a database/db.js file to manage the database connection:
import { Pool } from 'pg';
import dotenv from 'dotenv';
dotenv.config();
const pool = new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT,
});
export default pool;
Add your database credentials to a .env file:
DB_USER=your_username
DB_HOST=localhost
DB_NAME=login_signup_db
DB_PASSWORD=your_password
DB_PORT=5432
Step 2.3: Create the Users Table
Log in to the database and create a users table:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
3. Implementing the API Endpoints
Step 3.1: Creating the Signup Endpoint
First, let’s handle new user registration in controllers/authController.js:
import bcrypt from 'bcrypt';
import pool from '../database/db.js';
export const signup = async (req, res) => {
const { email, password } = req.body;
try {
// Check if the user already exists
const userExists = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
if (userExists.rows.length > 0) {
return res.status(400).json({ message: 'User already exists.' });
}
// Hash the password
const hashedPassword = await bcrypt.hash(password, 10);
// Insert the user into the database
await pool.query('INSERT INTO users (email, password) VALUES ($1, $2)', [email, hashedPassword]);
res.status(201).json({ message: 'User registered successfully!' });
} catch (error) {
res.status(500).json({ message: 'Server error.', error: error.message });
}
};
Add the route in routes/authRoutes.js:
import express from 'express';
import { signup } from '../controllers/authController.js';
const router = express.Router();
router.post('/signup', signup);
export default router;
Wire the route in app.js:
import express from 'express';
import bodyParser from 'body-parser';
import cors from 'cors';
import dotenv from 'dotenv';
import authRoutes from './routes/authRoutes.js';
dotenv.config();
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use('/api/auth', authRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Step 3.2: Creating the Login Endpoint
Add the login logic in authController.js:
export const login = async (req, res) => {
const { email, password } = req.body;
try {
// Check if the user exists
const user = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
if (user.rows.length === 0) {
return res.status(404).json({ message: 'User not found.' });
}
// Compare passwords
const isMatch = await bcrypt.compare(password, user.rows[0].password);
if (!isMatch) {
return res.status(401).json({ message: 'Invalid credentials.' });
}
res.status(200).json({ message: 'Login successful!' });
} catch (error) {
res.status(500).json({ message: 'Server error.', error: error.message });
}
};
Add the login route in authRoutes.js:
import { login } from '../controllers/authController.js';
router.post('/login', login);
4. Testing the API
With everything set up, it’s time to test! Use a tool like Postman or Insomnia to send requests to the API:
Signup Request
- Endpoint:
POST /api/auth/signup - Body:
{ "email": "user@example.com", "password": "securepassword" } - Response:
{ "message": "User registered successfully!" }
Login Request
- Endpoint:
POST /api/auth/login - Body:
{ "email": "user@example.com", "password": "securepassword" } - Response:
{ "message": "Login successful!" }
5. Final Thoughts
This was both a challenge and a joy! Designing the backend for scalability with a clean structure of routes, controllers, and middleware made me appreciate the beauty of well-organized code.
Also google is your greatest friend!!
If you’re building something similar, I’d love to hear about your process or help with any challenges you’re facing. Let’s grow together as developers!
#NodeJS #PostgreSQL #API #WebDevelopment #CodingJourney

Comments
Post a Comment
Say something Diego!