Total Pageviews

Complete Beginner's Guide to Node.js + Express

 

PART 1: INSTALLATION & SETUP

Step 1: Install Node.js

  1. Go to nodejs.org

  2. Download and install the LTS version (Long Term Support)

  3. Verify installation:

    bash
    # Open Terminal (Mac/Linux) or Command Prompt/PowerShell (Windows)
    node --version
    npm --version

    You should see version numbers like v20.x.x and 10.x.x

Step 2: Understanding the Tools

  • Node.js: Lets you run JavaScript outside the browser (on a server)

  • npm (Node Package Manager): Installs libraries/packages

  • Express: A web framework for Node.js (makes building websites/servers easier)

PART 2: YOUR FIRST NODE.JS PROGRAM

  1. Create a project folder:

    bash
    # Open terminal/command prompt
    mkdir my-first-project
    cd my-first-project
  2. Create your first JavaScript file:

    bash
    # Windows users can use Notepad or VS Code
    # Create a file named hello.js
  3. Add this code to hello.js:

    javascript
    console.log("Hello, World!");
  4. Run it:

    bash
    node hello.js

    You should see "Hello, World!" printed in the terminal.

PART 3: CREATING AN EXPRESS SERVER

Step 1: Initialize Your Project

bash
# In your project folder
npm init -y

This creates a package.json file (project configuration).

Step 2: Install Express

bash
npm install express

This creates a node_modules folder (where packages are stored).

Step 3: Create Your Server File

Create a file named server.js:

javascript
// 1. Import Express
const express = require('express');

// 2. Create an Express application
const app = express();

// 3. Define the port
const PORT = 3000;

// 4. Create your first route
app.get('/', (request, response) => {
    response.send('Welcome to my website!');
});

// 5. Start the server
app.listen(PORT, () => {
    console.log(`✅ Server is running at: http://localhost:${PORT}`);
});

Step 4: Run Your Server

bash
node server.js

Step 5: Visit Your Website

Open your browser and go to: http://localhost:3000

You should see "Welcome to my website!"

PART 4: UNDERSTANDING THE CODE

Let's break down server.js line by line:

javascript
// Line 1: Import the Express library
// Think of this like: "I want to use Express tools"
const express = require('express');

// Line 2: Create our app (like building a house blueprint)
const app = express();

// Line 3: Choose a door number (port) for our server
const PORT = 3000;

// Line 4-6: Create a route (like a room in the house)
// When someone visits the homepage ('/'), send a response
app.get('/', (req, res) => {
    res.send('Welcome to my website!');
});

// Line 8-10: Turn on the server (open the door)
app.listen(PORT, () => {
    console.log(`✅ Server is running at: http://localhost:${PORT}`);
});

PART 5: ADDING MORE PAGES/ROUTES

Update your server.js:

javascript
const express = require('express');
const app = express();
const PORT = 3000;

// Homepage
app.get('/', (req, res) => {
    res.send(`
        <h1>Welcome to My Website!</h1>
        <p>Check out these pages:</p>
        <ul>
            <li><a href="/about">About Us</a></li>
            <li><a href="/contact">Contact</a></li>
            <li><a href="/users/john">John's Profile</a></li>
        </ul>
    `);
});

// About page
app.get('/about', (req, res) => {
    res.send('<h1>About Us</h1><p>We are a learning company!</p>');
});

// Contact page
app.get('/contact', (req, res) => {
    res.send('<h1>Contact Page</h1><p>Email us at: hello@example.com</p>');
});

// Dynamic route (can handle any username)
app.get('/users/:username', (req, res) => {
    res.send(`<h1>Profile: ${req.params.username}</h1>`);
});

// JSON response (for APIs)
app.get('/api/data', (req, res) => {
    res.json({
        message: "Hello from API!",
        users: ["John", "Sarah", "Mike"],
        timestamp: new Date()
    });
});

app.listen(PORT, () => {
    console.log(`✅ Server running: http://localhost:${PORT}`);
});

PART 6: ADDING HTML FILES (PROPER WEBSITE)

Step 1: Create Folder Structure

text
my-first-project/
├── node_modules/
├── public/
│   ├── index.html
│   ├── about.html
│   └── styles.css
├── server.js
└── package.json

Step 2: Create HTML Files

public/index.html:

html
<!DOCTYPE html>
<html>
<head>
    <title>My Website</title>
    <link rel="stylesheet" href="/styles.css">
</head>
<body>
    <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
        <a href="/contact">Contact</a>
    </nav>
    
    <h1>Welcome to My Homepage!</h1>
    <p>This is served by Express.js</p>
    
    <button id="loadData">Load Data</button>
    <div id="data"></div>
    
    <script>
        document.getElementById('loadData').addEventListener('click', async () => {
            const response = await fetch('/api/data');
            const data = await response.json();
            document.getElementById('data').innerHTML = 
                `<pre>${JSON.stringify(data, null, 2)}</pre>`;
        });
    </script>
</body>
</html>

public/about.html:

html
<!DOCTYPE html>
<html>
<head>
    <title>About Us</title>
    <link rel="stylesheet" href="/styles.css">
</head>
<body>
    <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
        <a href="/contact">Contact</a>
    </nav>
    
    <h1>About Our Company</h1>
    <p>We teach beginners how to code!</p>
</body>
</html>

public/styles.css:

css
body {
    font-family: Arial, sans-serif;
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
    background: #f5f5f5;
}

nav {
    background: #333;
    padding: 15px;
    margin-bottom: 20px;
}

nav a {
    color: white;
    margin-right: 15px;
    text-decoration: none;
}

nav a:hover {
    text-decoration: underline;
}

button {
    background: #007bff;
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 5px;
    cursor: pointer;
}

button:hover {
    background: #0056b3;
}

Step 3: Update server.js

javascript
const express = require('express');
const app = express();
const PORT = 3000;

// Serve static files (HTML, CSS, images)
app.use(express.static('public'));

// API endpoint
app.get('/api/data', (req, res) => {
    res.json({
        message: "Hello from the server!",
        serverTime: new Date().toISOString(),
        status: "active"
    });
});

// Custom route for about page
app.get('/about', (req, res) => {
    res.sendFile(__dirname + '/public/about.html');
});

// Handle 404 errors
app.use((req, res) => {
    res.status(404).send(`
        <h1>404 - Page Not Found</h1>
        <p>The page you're looking for doesn't exist.</p>
        <a href="/">Go back home</a>
    `);
});

app.listen(PORT, () => {
    console.log(`✅ Server running: http://localhost:${PORT}`);
    console.log(`📁 Static files served from: /public`);
    console.log(`🔗 Homepage: http://localhost:${PORT}/index.html`);
});

PART 7: ADDING FORMS & PROCESSING DATA

Step 1: Add Contact Form

public/contact.html:

html
<!DOCTYPE html>
<html>
<head>
    <title>Contact Us</title>
    <link rel="stylesheet" href="/styles.css">
</head>
<body>
    <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
        <a href="/contact">Contact</a>
    </nav>
    
    <h1>Contact Us</h1>
    
    <form id="contactForm">
        <div>
            <label>Name:</label>
            <input type="text" name="name" required>
        </div>
        
        <div>
            <label>Email:</label>
            <input type="email" name="email" required>
        </div>
        
        <div>
            <label>Message:</label>
            <textarea name="message" rows="4" required></textarea>
        </div>
        
        <button type="submit">Send Message</button>
    </form>
    
    <div id="response"></div>
    
    <script>
        document.getElementById('contactForm').addEventListener('submit', async (e) => {
            e.preventDefault();
            
            const formData = new FormData(e.target);
            const data = Object.fromEntries(formData);
            
            try {
                const response = await fetch('/api/contact', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(data)
                });
                
                const result = await response.json();
                document.getElementById('response').innerHTML = 
                    `<p style="color: green;">${result.message}</p>`;
                
                e.target.reset();
            } catch (error) {
                document.getElementById('response').innerHTML = 
                    `<p style="color: red;">Error: ${error.message}</p>`;
            }
        });
    </script>
</body>
</html>

Step 2: Update server.js to Handle Form Data

javascript
const express = require('express');
const app = express();
const PORT = 3000;

// Middleware to parse JSON and form data
app.use(express.json()); // For JSON data
app.use(express.urlencoded({ extended: true })); // For form data

// Serve static files
app.use(express.static('public'));

// API endpoint for contact form
app.post('/api/contact', (req, res) => {
    console.log('Form submitted:', req.body);
    
    // In a real app, you would save to database here
    res.json({
        message: `Thank you ${req.body.name}! We received your message.`,
        receivedData: req.body
    });
});

// API endpoint to get data
app.get('/api/data', (req, res) => {
    res.json({
        message: "Hello from the server!",
        serverTime: new Date().toISOString(),
        status: "active"
    });
});

// Routes for HTML pages
app.get('/', (req, res) => {
    res.sendFile(__dirname + '/public/index.html');
});

app.get('/about', (req, res) => {
    res.sendFile(__dirname + '/public/about.html');
});

app.get('/contact', (req, res) => {
    res.sendFile(__dirname + '/public/contact.html');
});

// Handle 404 errors
app.use((req, res) => {
    res.status(404).send(`
        <h1>404 - Page Not Found</h1>
        <p>The page you're looking for doesn't exist.</p>
        <a href="/">Go back home</a>
    `);
});

app.listen(PORT, () => {
    console.log(`✅ Server running: http://localhost:${PORT}`);
    console.log('📌 Available pages:');
    console.log(`   Home:      http://localhost:${PORT}/`);
    console.log(`   About:     http://localhost:${PORT}/about`);
    console.log(`   Contact:   http://localhost:${PORT}/contact`);
    console.log(`   API Data:  http://localhost:${PORT}/api/data`);
});

PART 8: DEVELOPMENT TOOLS

Install Nodemon (Auto-restart server)

bash
npm install --save-dev nodemon

Update package.json:

json
{
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  }
}
Then it looks like
json
{
  "name": "my-first-project",
  "version": "1.0.0",
  "description": "",
  "main": "hello.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js",
    "dev": "nodemon server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "dependencies": {
    "express": "^5.2.1"
  },
  "devDependencies": {
    "nodemon": "^3.1.11"
  }
}

Now run with:

bash
npm run dev

Nodemon automatically restarts when you save changes!

Project Structure Final

text
my-first-project/
├── node_modules/
├── public/
│   ├── index.html
│   ├── about.html
│   ├── contact.html
│   └── styles.css
├── server.js
├── package.json
└── README.md

PART 9: TESTING YOUR APPLICATION

  1. Start the server:

    bash
    npm run dev
  2. Visit these URLs in your browser:

    • http://localhost:3000/ - Homepage

    • http://localhost:3000/about - About page

    • http://localhost:3000/contact - Contact form

    • http://localhost:3000/api/data - Raw JSON data

  3. Test the contact form:

    • Fill out the form on /contact

    • Click submit

    • Check browser console for response

    • Check terminal for server logs


Complete PostgreSQL + Node.js/Express Guide for Beginners

PART 1: POSTGRESQL INSTALLATION

Option A: Install PostgreSQL Locally

Windows:

  1. Download from postgresql.org/download/windows/

  2. Run installer, remember password you set for "postgres" user

  3. Check if installed: Search for "pgAdmin" or "SQL Shell" in Start Menu

Mac:

bash
# Using Homebrew (recommended)
brew install postgresql
brew services start postgresql

# Or download from postgresql.org/download/macosx/

Linux (Ubuntu/Debian):

bash
sudo apt update
sudo apt install postgresql postgresql-contrib
sudo systemctl start postgresql

Option B: Use Cloud Database (Easier for Beginners)

  1. Sign up for a free account:

  2. Get your connection string:

    text
    postgresql://username:password@host:port/database

PART 2: SET UP YOUR PROJECT

Step 1: Create Project Structure

bash
mkdir node-postgres-tutorial
cd node-postgres-tutorial
npm init -y

Step 2: Install Required Packages

bash
npm install express pg dotenv
npm install --save-dev nodemon

What each package does:

  • express: Web framework

  • pg: PostgreSQL client for Node.js

  • dotenv: Load environment variables from .env file

  • nodemon: Auto-restart server on changes

Step 3: Basic Project Structure

text
node-postgres-tutorial/
├── node_modules/
├── src/
│   ├── config/
│   │   └── database.js
│   ├── controllers/
│   │   └── userController.js
│   ├── routes/
│   │   └── userRoutes.js
│   └── server.js
├── .env
├── .gitignore
├── package.json
└── README.md

PART 3: DATABASE CONNECTION

Step 1: Create .env file

env
# Database Connection
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=yourpassword
DB_NAME=mydatabase

# Server
PORT=3000
NODE_ENV=development

# For cloud databases, use connection string:
# DATABASE_URL=postgresql://username:password@host:port/database

Step 2: Create database configuration

src/config/database.js:

javascript
const { Pool } = require('pg');
require('dotenv').config();

// Create a connection pool
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,
  // For cloud databases, use connection string:
  // connectionString: process.env.DATABASE_URL,
  ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
});

// Test connection
pool.connect((err, client, release) => {
  if (err) {
    console.error('❌ Error connecting to PostgreSQL:', err.message);
  } else {
    console.log('✅ Successfully connected to PostgreSQL database');
    release();
  }
});

module.exports = {
  query: (text, params) => pool.query(text, params),
  pool
};

PART 4: INITIAL DATABASE SETUP

Step 1: Create SQL Script

Create database-setup.sql:

sql
-- Create database (run this in PostgreSQL shell or pgAdmin first)
-- CREATE DATABASE mydatabase;

-- Connect to your database first, then run:

-- Create users table
CREATE TABLE IF NOT EXISTS users (
  id SERIAL PRIMARY KEY,
  username VARCHAR(50) UNIQUE NOT NULL,
  email VARCHAR(100) UNIQUE NOT NULL,
  password VARCHAR(255) NOT NULL,
  age INTEGER,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Create posts table
CREATE TABLE IF NOT EXISTS posts (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
  title VARCHAR(255) NOT NULL,
  content TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Insert sample data
INSERT INTO users (username, email, password, age) VALUES
('john_doe', 'john@example.com', 'hashed_password_123', 25),
('jane_smith', 'jane@example.com', 'hashed_password_456', 30),
('bob_wilson', 'bob@example.com', 'hashed_password_789', 22)
ON CONFLICT (username) DO NOTHING;

INSERT INTO posts (user_id, title, content) VALUES
(1, 'My First Post', 'Hello everyone, this is my first post!'),
(1, 'Learning PostgreSQL', 'PostgreSQL is amazing for web apps.'),
(2, 'Node.js Tips', 'Here are some Node.js tips for beginners.')
ON CONFLICT DO NOTHING;

-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_posts_user_id ON posts(user_id);

Step 2: How to Run SQL Script

Using psql (Command Line):

bash
# Connect to PostgreSQL
psql -U postgres -h localhost

# Create database
CREATE DATABASE mydatabase;

# Connect to database
\c mydatabase

# Run SQL file
\i database-setup.sql
with full path
\i 'C:/my-first-project/node-postgres-tutorial/database-setup.sql'

Using pgAdmin (GUI):

  1. Open pgAdmin

  2. Connect to your server

  3. Right-click on Databases → Create → Database

  4. Name it "mydatabase"

  5. Right-click on "mydatabase" → Query Tool

  6. Copy-paste SQL from database-setup.sql and run

PART 5: BASIC SERVER WITH DATABASE

Step 1: Create Main Server File

src/server.js:

javascript
const express = require('express');
require('dotenv').config();
const db = require('./config/database');

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Basic route
app.get('/', (req, res) => {
  res.json({
    message: 'Welcome to Node.js + PostgreSQL API',
    endpoints: {
      users: '/api/users',
      userById: '/api/users/:id',
      posts: '/api/posts'
    },
    database: 'PostgreSQL',
    status: 'connected'
  });
});

// Test database connection
app.get('/health', async (req, res) => {
  try {
    const result = await db.query('SELECT NOW()');
    res.json({
      status: 'healthy',
      database: 'connected',
      timestamp: result.rows[0].now
    });
  } catch (error) {
    res.status(500).json({
      status: 'unhealthy',
      database: 'disconnected',
      error: error.message
    });
  }
});

// Start server
app.listen(PORT, () => {
  console.log(`🚀 Server running on http://localhost:${PORT}`);
  console.log(`📊 Database: PostgreSQL`);
  console.log(`⚡ Environment: ${process.env.NODE_ENV}`);
});

Step 2: Update package.json

json
{
  "name": "node-postgres-tutorial",
  "version": "1.0.0",
  "scripts": {
    "start": "node src/server.js",
    "dev": "nodemon src/server.js",
    "setup-db": "psql -U postgres -f database-setup.sql"
  },
  "dependencies": {
    "express": "^4.18.2",
    "pg": "^8.11.0",
    "dotenv": "^16.3.1"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}
Looks like this
{
  "name": "node-postgres-tutorial",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node src/server.js",
    "dev": "nodemon src/server.js",
    "setup-db": "psql -U postgres -f database-setup.sql",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "dependencies": {
    "dotenv": "^17.2.3",
    "express": "^5.2.1",
    "pg": "^8.16.3"
  },
  "devDependencies": {
    "nodemon": "^3.1.11"
  }
}

Step 3: Run Your Server

bash
npm run dev

Visit http://localhost:3000/health to test database connection!

PART 6: CRUD OPERATIONS - USERS

Step 1: Create User Controller

src/controllers/userController.js:

javascript
const db = require('../config/database');

// Get all users
const getAllUsers = async (req, res) => {
  try {
    const result = await db.query(
      'SELECT id, username, email, age, created_at FROM users ORDER BY id'
    );
    res.json({
      success: true,
      count: result.rowCount,
      users: result.rows
    });
  } catch (error) {
    console.error('Error fetching users:', error);
    res.status(500).json({
      success: false,
      message: 'Error fetching users',
      error: error.message
    });
  }
};

// Get single user by ID
const getUserById = async (req, res) => {
  try {
    const { id } = req.params;
    const result = await db.query(
      'SELECT id, username, email, age, created_at FROM users WHERE id = $1',
      [id]
    );
    
    if (result.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }
    
    res.json({
      success: true,
      user: result.rows[0]
    });
  } catch (error) {
    console.error('Error fetching user:', error);
    res.status(500).json({
      success: false,
      message: 'Error fetching user',
      error: error.message
    });
  }
};

// Create new user
const createUser = async (req, res) => {
  try {
    const { username, email, password, age } = req.body;
    
    // Basic validation
    if (!username || !email || !password) {
      return res.status(400).json({
        success: false,
        message: 'Username, email, and password are required'
      });
    }
    
    const result = await db.query(
      `INSERT INTO users (username, email, password, age) 
       VALUES ($1, $2, $3, $4) 
       RETURNING id, username, email, age, created_at`,
      [username, email, password, age || null]
    );
    
    res.status(201).json({
      success: true,
      message: 'User created successfully',
      user: result.rows[0]
    });
  } catch (error) {
    console.error('Error creating user:', error);
    
    // Handle duplicate username/email
    if (error.code === '23505') { // Unique violation
      return res.status(409).json({
        success: false,
        message: 'Username or email already exists'
      });
    }
    
    res.status(500).json({
      success: false,
      message: 'Error creating user',
      error: error.message
    });
  }
};

// Update user
const updateUser = async (req, res) => {
  try {
    const { id } = req.params;
    const { username, email, age } = req.body;
    
    // Check if user exists
    const userCheck = await db.query('SELECT id FROM users WHERE id = $1', [id]);
    if (userCheck.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }
    
    const result = await db.query(
      `UPDATE users 
       SET username = COALESCE($1, username),
           email = COALESCE($2, email),
           age = COALESCE($3, age),
           updated_at = CURRENT_TIMESTAMP
       WHERE id = $4
       RETURNING id, username, email, age, updated_at`,
      [username, email, age, id]
    );
    
    res.json({
      success: true,
      message: 'User updated successfully',
      user: result.rows[0]
    });
  } catch (error) {
    console.error('Error updating user:', error);
    res.status(500).json({
      success: false,
      message: 'Error updating user',
      error: error.message
    });
  }
};

// Delete user
const deleteUser = async (req, res) => {
  try {
    const { id } = req.params;
    
    const result = await db.query(
      'DELETE FROM users WHERE id = $1 RETURNING id',
      [id]
    );
    
    if (result.rowCount === 0) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }
    
    res.json({
      success: true,
      message: 'User deleted successfully'
    });
  } catch (error) {
    console.error('Error deleting user:', error);
    res.status(500).json({
      success: false,
      message: 'Error deleting user',
      error: error.message
    });
  }
};

// Search users
const searchUsers = async (req, res) => {
  try {
    const { query } = req.query;
    
    if (!query) {
      return res.status(400).json({
        success: false,
        message: 'Search query is required'
      });
    }
    
    const searchPattern = `%${query}%`;
    const result = await db.query(
      `SELECT id, username, email, age, created_at 
       FROM users 
       WHERE username ILIKE $1 OR email ILIKE $1
       ORDER BY username`,
      [searchPattern]
    );
    
    res.json({
      success: true,
      count: result.rowCount,
      users: result.rows
    });
  } catch (error) {
    console.error('Error searching users:', error);
    res.status(500).json({
      success: false,
      message: 'Error searching users',
      error: error.message
    });
  }
};

module.exports = {
  getAllUsers,
  getUserById,
  createUser,
  updateUser,
  deleteUser,
  searchUsers
};

Step 2: Create User Routes

src/routes/userRoutes.js:

javascript
const express = require('express');
const router = express.Router();
const {
  getAllUsers,
  getUserById,
  createUser,
  updateUser,
  deleteUser,
  searchUsers
} = require('../controllers/userController');

// GET all users
router.get('/', getAllUsers);

// GET single user
router.get('/:id', getUserById);

// POST create new user
router.post('/', createUser);

// PUT update user
router.put('/:id', updateUser);

// DELETE user
router.delete('/:id', deleteUser);

// SEARCH users
router.get('/search', searchUsers);

module.exports = router;

Step 3: Update Server to Use Routes

Update src/server.js:

javascript
const express = require('express');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 3000;

// Import routes
const userRoutes = require('./routes/userRoutes');

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Routes
app.use('/api/users', userRoutes);

// Basic routes
app.get('/', (req, res) => {
  res.json({
    message: 'Welcome to Node.js + PostgreSQL API',
    endpoints: {
      users: {
        getAll: 'GET /api/users',
        getSingle: 'GET /api/users/:id',
        create: 'POST /api/users',
        update: 'PUT /api/users/:id',
        delete: 'DELETE /api/users/:id',
        search: 'GET /api/users/search?query=term'
      }
    }
  });
});

// Health check
app.get('/health', async (req, res) => {
  try {
    const db = require('./config/database');
    const result = await db.query('SELECT NOW()');
    res.json({
      status: 'healthy',
      database: 'connected',
      timestamp: result.rows[0].now
    });
  } catch (error) {
    res.status(500).json({
      status: 'unhealthy',
      database: 'disconnected',
      error: error.message
    });
  }
});

// 404 handler
app.use((err, req, res, next) => {
res.status(404).json({ success: false, message: `Route ${req.originalUrl} not found` }); }); // Error handler app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ success: false, message: 'Something went wrong!', error: process.env.NODE_ENV === 'development' ? err.message : undefined }); }); // Start server app.listen(PORT, () => { console.log(`🚀 Server running on http://localhost:${PORT}`); console.log(`📊 Database: PostgreSQL`); console.log(`⚡ Environment: ${process.env.NODE_ENV}`); console.log(`📝 API Documentation: http://localhost:${PORT}`); });

PART 7: TESTING THE API

Using Browser:

  1. http://localhost:3000/ - API documentation

  2. http://localhost:3000/health - Health check

  3. http://localhost:3000/api/users - Get all users

  4. http://localhost:3000/api/users/1 - Get user by ID

  5. http://localhost:3000/api/users/search?query=john - Search users

Using curl (Command Line):

bash
# Get all users
curl http://localhost:3000/api/users

# Get user by ID
curl http://localhost:3000/api/users/1

# Create new user
curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"test_user","email":"test@example.com","password":"test123","age":28}'

# Update user
curl -X PUT http://localhost:3000/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{"email":"newemail@example.com"}'

# Delete user
curl -X DELETE http://localhost:3000/api/users/4

Using Postman or Thunder Client:

  1. Install Thunder Client (VS Code extension) or Postman

  2. Create requests for each endpoint

  3. Test with different data

PART 8: ADVANCED FEATURES

1. Pagination

Update userController.js:

javascript
const getAllUsersPaginated = async (req, res) => {
  try {
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 10;
    const offset = (page - 1) * limit;
    
    // Get total count
    const countResult = await db.query('SELECT COUNT(*) FROM users');
    const total = parseInt(countResult.rows[0].count);
    const totalPages = Math.ceil(total / limit);
    
    // Get paginated users
    const result = await db.query(
      `SELECT id, username, email, age, created_at 
       FROM users 
       ORDER BY id 
       LIMIT $1 OFFSET $2`,
      [limit, offset]
    );
    
    res.json({
      success: true,
      pagination: {
        page,
        limit,
        total,
        totalPages,
        hasNext: page < totalPages,
        hasPrev: page > 1
      },
      users: result.rows
    });
  } catch (error) {
    console.error('Error fetching users:', error);
    res.status(500).json({
      success: false,
      message: 'Error fetching users',
      error: error.message
    });
  }
};

Usage: GET /api/users?page=1&limit=5

2. Transactions

javascript
const createUserWithTransaction = async (req, res) => {
  const client = await db.pool.connect();
  
  try {
    await client.query('BEGIN');
    
    const { username, email, password, age } = req.body;
    
    // Insert user
    const userResult = await client.query(
      `INSERT INTO users (username, email, password, age) 
       VALUES ($1, $2, $3, $4) 
       RETURNING id`,
      [username, email, password, age]
    );
    
    const userId = userResult.rows[0].id;
    
    // Create default post for user
    await client.query(
      `INSERT INTO posts (user_id, title, content) 
       VALUES ($1, $2, $3)`,
      [userId, 'Welcome Post', 'Welcome to our platform!']
    );
    
    await client.query('COMMIT');
    
    res.status(201).json({
      success: true,
      message: 'User created with welcome post',
      userId
    });
  } catch (error) {
    await client.query('ROLLBACK');
    console.error('Transaction error:', error);
    res.status(500).json({
      success: false,
      message: 'Transaction failed',
      error: error.message
    });
  } finally {
    client.release();
  }
};

3. Connection Pool Monitoring

src/config/database.js - Extended:

javascript
const { Pool } = require('pg');
require('dotenv').config();

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,
  max: 20, // Maximum number of clients in the pool
  idleTimeoutMillis: 30000, // How long a client is allowed to remain idle
  connectionTimeoutMillis: 2000, // How long to wait for a connection
});

// Log pool events
pool.on('connect', () => {
  console.log('New client connected to PostgreSQL pool');
});

pool.on('error', (err, client) => {
  console.error('Unexpected error on idle client', err);
});

pool.on('remove', () => {
  console.log('Client removed from pool');
});

// Add pool stats endpoint
const getPoolStats = () => {
  return {
    totalCount: pool.totalCount,
    idleCount: pool.idleCount,
    waitingCount: pool.waitingCount
  };
};

module.exports = {
  query: (text, params) => pool.query(text, params),
  pool,
  getPoolStats
};

4. Environment-specific Configurations

.env.development:

env
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=dev_password
DB_NAME=mydatabase_dev
PORT=3000
NODE_ENV=development

.env.production:

env
DATABASE_URL=postgresql://username:password@host:port/production_db
PORT=80
NODE_ENV=production

Update package.json:

json
{
  "scripts": {
    "start": "node src/server.js",
    "dev": "nodemon src/server.js",
    "dev:env": "NODE_ENV=development nodemon src/server.js",
    "prod": "NODE_ENV=production node src/server.js"
  }
}

PART 9: TROUBLESHOOTING

Common Errors & Solutions:

  1. "password authentication failed for user"

    bash
    # Check your .env file
    # Reset password: 
    sudo -u postgres psql
    \password postgres
  2. "database does not exist"

    bash
    # Create database
    sudo -u postgres createdb mydatabase
  3. "Connection refused"

    bash
    # Check if PostgreSQL is running
    sudo systemctl status postgresql
    # Start if stopped
    sudo systemctl start postgresql
  4. "Too many clients already"

    javascript
    // Increase pool size in database.js
    max: 50, // Increase this number

Debug Queries:

javascript
// Add to database.js for debugging
const queryWithLog = async (text, params) => {
  const start = Date.now();
  const res = await pool.query(text, params);
  const duration = Date.now() - start;
  console.log('Executed query', { text, duration, rows: res.rowCount });
  return res;
};

PART 10: DEPLOYMENT

Option 1: Railway.app (Easiest for Beginners)

  1. Push code to GitHub

  2. Sign up at railway.app

  3. Create new project → Deploy from GitHub repo

  4. Add PostgreSQL plugin

  5. Railway automatically sets DATABASE_URL environment variable

Option 2: Heroku

bash
# Install Heroku CLI
heroku login
heroku create your-app-name
heroku addons:create heroku-postgresql:hobby-dev
git push heroku main

COMPLETE EXAMPLE PROJECT

Download the complete project structure:

bash
# Create project with all files
mkdir complete-postgres-project
cd complete-postgres-project

# Initialize and install
npm init -y
npm install express pg dotenv

# Create all the files as shown above

NEXT STEPS TO LEARN:

  1. Authentication (JWT tokens)

  2. Data Validation (with Joi or Zod)

  3. ORM/Query Builders (Prisma, Sequelize, or Knex.js)

  4. Database Migrations

  5. Performance Optimization (Indexing, Query optimization)

  6. Backup & Recovery

QUICK REFERENCE - All Commands:

bash
# Project setup
npm init -y
npm install express pg dotenv
npm install --save-dev nodemon

# Start development server
npm run dev

# Database commands
psql -U postgres                         # Connect to PostgreSQL
\l                                       # List databases
\c database_name                         # Connect to database
\dt                                      # List tables
\q                                       # Quit

# API Testing with curl
curl http://localhost:3000/api/users
curl -X POST http://localhost:3000/api/users -H "Content-Type: application/json" -d '{"username":"test"}'

Remember: Always use environment variables for sensitive data, never commit .env to GitHub!

Complete User Authentication System for Beginners

PART 1: AUTHENTICATION CONCEPTS EXPLAINED SIMPLY

What is Authentication?

  • Register: Create new account (sign up)

  • Login: Access existing account (sign in)

  • Logout: End your session

  • Protected Routes: Pages only logged-in users can see

Tools We'll Use:

  1. bcryptjs - Hash passwords (never store plain passwords!)

  2. jsonwebtoken (JWT) - Create secure tokens for sessions

  3. express-validator - Validate user input

  4. cookie-parser - Handle cookies for web browsers

PART 2: PROJECT SETUP

Step 1: Create New Project

bash
mkdir auth-tutorial
cd auth-tutorial
npm init -y

Step 2: Install All Packages

bash
npm install express pg dotenv bcryptjs jsonwebtoken cookie-parser express-validator cors
npm install --save-dev nodemon

Step 3: Project Structure

text
auth-tutorial/
├── node_modules/
├── src/
│   ├── config/
│   │   └── database.js
│   ├── controllers/
│   │   └── authController.js
│   ├── middleware/
│   │   ├── authMiddleware.js
│   │   └── validationMiddleware.js
│   ├── routes/
│   │   └── authRoutes.js
│   ├── utils/
│   │   └── jwtUtils.js
│   └── server.js
├── .env
├── .gitignore
├── package.json
└── README.md

Step 4: Environment Variables (.env)

env
# Server
PORT=3000
NODE_ENV=development

# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=yourpassword
DB_NAME=auth_demo

# JWT Secrets (NEVER share these!)
JWT_SECRET=your_super_secret_jwt_key_change_this_in_production
JWT_REFRESH_SECRET=your_super_secret_refresh_key_change_this_too
JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d

# Security
BCRYPT_SALT_ROUNDS=10

PART 3: DATABASE SETUP

Step 1: Create Database Schema

Create database-setup.sql:

sql
-- Create database (run this first in PostgreSQL)
-- CREATE DATABASE auth_demo;

-- Users table
CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    is_active BOOLEAN DEFAULT true,
    is_verified BOOLEAN DEFAULT false,
    role VARCHAR(20) DEFAULT 'user',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- User profiles table (optional, for additional user info)
CREATE TABLE IF NOT EXISTS user_profiles (
    id SERIAL PRIMARY KEY,
    user_id INTEGER UNIQUE REFERENCES users(id) ON DELETE CASCADE,
    bio TEXT,
    avatar_url VARCHAR(500),
    phone VARCHAR(20),
    birthdate DATE,
    address TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Password reset tokens
CREATE TABLE IF NOT EXISTS password_reset_tokens (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    token VARCHAR(255) NOT NULL,
    expires_at TIMESTAMP NOT NULL,
    used BOOLEAN DEFAULT false,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Email verification tokens
CREATE TABLE IF NOT EXISTS verification_tokens (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    token VARCHAR(255) NOT NULL,
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- User sessions (for refresh tokens)
CREATE TABLE IF NOT EXISTS user_sessions (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    refresh_token VARCHAR(500) NOT NULL,
    user_agent TEXT,
    ip_address VARCHAR(45),
    expires_at TIMESTAMP NOT NULL,
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Create indexes for better performance
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_password_reset_tokens_token ON password_reset_tokens(token);
CREATE INDEX idx_verification_tokens_token ON verification_tokens(token);
CREATE INDEX idx_user_sessions_refresh_token ON user_sessions(refresh_token);

-- Insert admin user (password: admin123)
-- You can run this after setting up the registration

Step 2: Database Configuration

src/config/database.js:

javascript
const { Pool } = require('pg');
require('dotenv').config();

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,
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

// Test connection
pool.connect((err, client, release) => {
  if (err) {
    console.error('❌ Error connecting to PostgreSQL:', err.message);
  } else {
    console.log('✅ Connected to PostgreSQL database');
    release();
  }
});

module.exports = {
  query: (text, params) => pool.query(text, params),
  pool
};

Step 2: Password Utilities

src/utils/passwordUtils.js:

javascript
const bcrypt = require('bcryptjs');

// Hash password
const hashPassword = async (password) => {
  const saltRounds = parseInt(process.env.BCRYPT_SALT_ROUNDS) || 10;
  return await bcrypt.hash(password, saltRounds);
};

// Compare password with hash
const comparePassword = async (password, hash) => {
  return await bcrypt.compare(password, hash);
};

// Generate random password (for password reset)
const generateRandomPassword = (length = 12) => {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
  let password = '';
  for (let i = 0; i < length; i++) {
    password += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return password;
};

module.exports = {
  hashPassword,
  comparePassword,
  generateRandomPassword
};

PART 5: VALIDATION MIDDLEWARE

src/middleware/validationMiddleware.js:

javascript
const { body, validationResult } = require('express-validator');

// Common validation rules
const validationRules = {
  // Register validation
  register: [
    body('email')
      .isEmail().normalizeEmail().withMessage('Please provide a valid email')
      .notEmpty().withMessage('Email is required'),
    
    body('username')
      .trim()
      .notEmpty().withMessage('Username is required')
      .isLength({ min: 3, max: 50 }).withMessage('Username must be between 3-50 characters')
      .matches(/^[a-zA-Z0-9_]+$/).withMessage('Username can only contain letters, numbers, and underscores'),
    
    body('password')
      .notEmpty().withMessage('Password is required')
      .isLength({ min: 6 }).withMessage('Password must be at least 6 characters')
      .matches(/[A-Z]/).withMessage('Password must contain at least one uppercase letter')
      .matches(/[a-z]/).withMessage('Password must contain at least one lowercase letter')
      .matches(/[0-9]/).withMessage('Password must contain at least one number'),
    
    body('confirmPassword')
      .notEmpty().withMessage('Please confirm your password')
      .custom((value, { req }) => value === req.body.password)
      .withMessage('Passwords do not match'),
    
    body('firstName')
      .optional()
      .trim()
      .isLength({ min: 2, max: 50 }).withMessage('First name must be between 2-50 characters'),
    
    body('lastName')
      .optional()
      .trim()
      .isLength({ min: 2, max: 50 }).withMessage('Last name must be between 2-50 characters'),
  ],

  // Login validation
  login: [
    body('email')
      .optional()
      .isEmail().normalizeEmail().withMessage('Please provide a valid email'),
    
    body('username')
      .optional()
      .trim()
      .notEmpty().withMessage('Username or email is required'),
    
    body('password')
      .notEmpty().withMessage('Password is required'),
    
    // Custom validation to ensure either email or username is provided
    body().custom((value, { req }) => {
      if (!req.body.email && !req.body.username) {
        throw new Error('Either email or username is required');
      }
      return true;
    }),
  ],

  // Change password validation
  changePassword: [
    body('currentPassword')
      .notEmpty().withMessage('Current password is required'),
    
    body('newPassword')
      .notEmpty().withMessage('New password is required')
      .isLength({ min: 6 }).withMessage('Password must be at least 6 characters')
      .matches(/[A-Z]/).withMessage('Password must contain at least one uppercase letter')
      .matches(/[a-z]/).withMessage('Password must contain at least one lowercase letter')
      .matches(/[0-9]/).withMessage('Password must contain at least one number')
      .custom((value, { req }) => value !== req.body.currentPassword)
      .withMessage('New password must be different from current password'),
    
    body('confirmNewPassword')
      .notEmpty().withMessage('Please confirm your new password')
      .custom((value, { req }) => value === req.body.newPassword)
      .withMessage('Passwords do not match'),
  ],

  // Forgot password validation
  forgotPassword: [
    body('email')
      .isEmail().normalizeEmail().withMessage('Please provide a valid email')
      .notEmpty().withMessage('Email is required'),
  ],

  // Reset password validation
  resetPassword: [
    body('password')
      .notEmpty().withMessage('Password is required')
      .isLength({ min: 6 }).withMessage('Password must be at least 6 characters')
      .matches(/[A-Z]/).withMessage('Password must contain at least one uppercase letter')
      .matches(/[a-z]/).withMessage('Password must contain at least one lowercase letter')
      .matches(/[0-9]/).withMessage('Password must contain at least one number'),
    
    body('confirmPassword')
      .notEmpty().withMessage('Please confirm your password')
      .custom((value, { req }) => value === req.body.password)
      .withMessage('Passwords do not match'),
  ],

  // Update profile validation
  updateProfile: [
    body('firstName')
      .optional()
      .trim()
      .isLength({ min: 2, max: 50 }).withMessage('First name must be between 2-50 characters'),
    
    body('lastName')
      .optional()
      .trim()
      .isLength({ min: 2, max: 50 }).withMessage('Last name must be between 2-50 characters'),
    
    body('username')
      .optional()
      .trim()
      .isLength({ min: 3, max: 50 }).withMessage('Username must be between 3-50 characters')
      .matches(/^[a-zA-Z0-9_]+$/).withMessage('Username can only contain letters, numbers, and underscores'),
    
    body('email')
      .optional()
      .isEmail().normalizeEmail().withMessage('Please provide a valid email'),
  ],
};

// Validation middleware
const validate = (validations) => {
  return async (req, res, next) => {
    // Run all validations
    await Promise.all(validations.map(validation => validation.run(req)));
    
    // Check for errors
    const errors = validationResult(req);
    if (errors.isEmpty()) {
      return next();
    }
    
    // Format errors
    const formattedErrors = errors.array().map(error => ({
      field: error.path,
      message: error.msg
    }));
    
    res.status(400).json({
      success: false,
      message: 'Validation failed',
      errors: formattedErrors
    });
  };
};

module.exports = {
  validationRules,
  validate
};

PART 6: AUTHENTICATION MIDDLEWARE

src/middleware/authMiddleware.js:

javascript
const { verifyAccessToken } = require('../utils/jwtUtils');
const db = require('../config/database');

// Middleware to check if user is authenticated
const authenticate = async (req, res, next) => {
  try {
    // Get token from header or cookie
    let token = req.headers.authorization || req.cookies.access_token;
    
    if (!token) {
      return res.status(401).json({
        success: false,
        message: 'Access denied. No token provided.'
      });
    }

    // Remove 'Bearer ' prefix if present
    if (token.startsWith('Bearer ')) {
      token = token.slice(7);
    }

    // Verify token
    const decoded = verifyAccessToken(token);
    
    if (!decoded) {
      return res.status(401).json({
        success: false,
        message: 'Invalid or expired token'
      });
    }

    // Check if user exists and is active
    const userResult = await db.query(
      'SELECT id, email, username, role, is_active, is_verified FROM users WHERE id = $1',
      [decoded.userId]
    );

    if (userResult.rows.length === 0) {
      return res.status(401).json({
        success: false,
        message: 'User not found'
      });
    }

    if (!userResult.rows[0].is_active) {
      return res.status(401).json({
        success: false,
        message: 'Account is deactivated'
      });
    }

    // Attach user to request object
    req.user = userResult.rows[0];
    next();
  } catch (error) {
    console.error('Authentication error:', error);
    res.status(500).json({
      success: false,
      message: 'Authentication failed'
    });
  }
};

// Middleware to check if user is verified
const requireVerified = (req, res, next) => {
  if (!req.user.is_verified) {
    return res.status(403).json({
      success: false,
      message: 'Please verify your email address first'
    });
  }
  next();
};

// Middleware to check user role
const requireRole = (...roles) => {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({
        success: false,
        message: 'Authentication required'
      });
    }

    if (!roles.includes(req.user.role)) {
      return res.status(403).json({
        success: false,
        message: 'Insufficient permissions'
      });
    }

    next();
  };
};

// Middleware to check if user is the owner or has admin role
const requireOwnershipOrAdmin = (paramName = 'id') => {
  return async (req, res, next) => {
    try {
      const resourceId = req.params[paramName];
      const userId = req.user.id;
      const userRole = req.user.role;

      // Admins can access anything
      if (userRole === 'admin') {
        return next();
      }

      // User can only access their own resources
      if (parseInt(resourceId) !== userId) {
        return res.status(403).json({
          success: false,
          message: 'You can only access your own resources'
        });
      }

      next();
    } catch (error) {
      console.error('Ownership check error:', error);
      res.status(500).json({
        success: false,
        message: 'Permission check failed'
      });
    }
  };
};

module.exports = {
  authenticate,
  requireVerified,
  requireRole,
  requireOwnershipOrAdmin
};

PART 7: AUTHENTICATION CONTROLLER

src/controllers/authController.js:

javascript
const db = require('../config/database');
const { hashPassword, comparePassword } = require('../utils/passwordUtils');
const { 
  generateAccessToken, 
  generateRefreshToken,
  verifyRefreshToken 
} = require('../utils/jwtUtils');

// Helper function to get user by email or username
const getUserByIdentifier = async (identifier) => {
  // Check if identifier is email
  if (identifier.includes('@')) {
    const result = await db.query(
      'SELECT * FROM users WHERE email = $1',
      [identifier]
    );
    return result.rows[0];
  }
  
  // Otherwise treat as username
  const result = await db.query(
    'SELECT * FROM users WHERE username = $1',
    [identifier]
  );
  return result.rows[0];
};

// Register User
const register = async (req, res) => {
  try {
    const { email, username, password, firstName, lastName } = req.body;

    // Check if user already exists
    const existingUser = await db.query(
      'SELECT id FROM users WHERE email = $1 OR username = $2',
      [email, username]
    );

    if (existingUser.rows.length > 0) {
      return res.status(409).json({
        success: false,
        message: 'User with this email or username already exists'
      });
    }

    // Hash password
    const passwordHash = await hashPassword(password);

    // Create user
    const result = await db.query(
      `INSERT INTO users (email, username, password_hash, first_name, last_name) 
       VALUES ($1, $2, $3, $4, $5) 
       RETURNING id, email, username, first_name, last_name, role, created_at`,
      [email, username, passwordHash, firstName, lastName]
    );

    const user = result.rows[0];

    // Generate tokens
    const accessToken = generateAccessToken(user.id, user.email, user.role);
    const refreshToken = generateRefreshToken(user.id);

    // Store refresh token in database
    await db.query(
      `INSERT INTO user_sessions (user_id, refresh_token, user_agent, ip_address, expires_at) 
       VALUES ($1, $2, $3, $4, NOW() + INTERVAL '7 days')`,
      [user.id, refreshToken, req.headers['user-agent'], req.ip]
    );

    // Set cookies (for web browsers)
    res.cookie('access_token', accessToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict',
      maxAge: 15 * 60 * 1000 // 15 minutes
    });

    res.cookie('refresh_token', refreshToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict',
      maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
    });

    res.status(201).json({
      success: true,
      message: 'Registration successful',
      user: {
        id: user.id,
        email: user.email,
        username: user.username,
        firstName: user.first_name,
        lastName: user.last_name,
        role: user.role
      },
      tokens: {
        accessToken,
        refreshToken
      }
    });

  } catch (error) {
    console.error('Registration error:', error);
    res.status(500).json({
      success: false,
      message: 'Registration failed',
      error: process.env.NODE_ENV === 'development' ? error.message : undefined
    });
  }
};

// Login User
const login = async (req, res) => {
  try {
    const { email, username, password } = req.body;
    const identifier = email || username;

    // Get user
    const user = await getUserByIdentifier(identifier);
    
    if (!user) {
      return res.status(401).json({
        success: false,
        message: 'Invalid credentials'
      });
    }

    // Check if account is active
    if (!user.is_active) {
      return res.status(401).json({
        success: false,
        message: 'Account is deactivated. Please contact support.'
      });
    }

    // Verify password
    const isValidPassword = await comparePassword(password, user.password_hash);
    
    if (!isValidPassword) {
      return res.status(401).json({
        success: false,
        message: 'Invalid credentials'
      });
    }

    // Generate tokens
    const accessToken = generateAccessToken(user.id, user.email, user.role);
    const refreshToken = generateRefreshToken(user.id);

    // Store refresh token in database
    await db.query(
      `INSERT INTO user_sessions (user_id, refresh_token, user_agent, ip_address, expires_at) 
       VALUES ($1, $2, $3, $4, NOW() + INTERVAL '7 days')`,
      [user.id, refreshToken, req.headers['user-agent'], req.ip]
    );

    // Update last login (you might want to add this field to users table)
    await db.query(
      'UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = $1',
      [user.id]
    );

    // Set cookies
    res.cookie('access_token', accessToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict',
      maxAge: 15 * 60 * 1000
    });

    res.cookie('refresh_token', refreshToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict',
      maxAge: 7 * 24 * 60 * 60 * 1000
    });

    res.json({
      success: true,
      message: 'Login successful',
      user: {
        id: user.id,
        email: user.email,
        username: user.username,
        firstName: user.first_name,
        lastName: user.last_name,
        role: user.role,
        isVerified: user.is_verified
      },
      tokens: {
        accessToken,
        refreshToken
      }
    });

  } catch (error) {
    console.error('Login error:', error);
    res.status(500).json({
      success: false,
      message: 'Login failed',
      error: process.env.NODE_ENV === 'development' ? error.message : undefined
    });
  }
};

// Logout User
const logout = async (req, res) => {
  try {
    const refreshToken = req.cookies.refresh_token || req.body.refreshToken;

    if (refreshToken) {
      // Remove refresh token from database
      await db.query(
        'UPDATE user_sessions SET is_active = false WHERE refresh_token = $1',
        [refreshToken]
      );
    }

    // Clear cookies
    res.clearCookie('access_token');
    res.clearCookie('refresh_token');

    res.json({
      success: true,
      message: 'Logged out successfully'
    });

  } catch (error) {
    console.error('Logout error:', error);
    res.status(500).json({
      success: false,
      message: 'Logout failed'
    });
  }
};

// Refresh Access Token
const refreshToken = async (req, res) => {
  try {
    const refreshToken = req.cookies.refresh_token || req.body.refreshToken;

    if (!refreshToken) {
      return res.status(401).json({
        success: false,
        message: 'Refresh token required'
      });
    }

    // Verify refresh token
    const decoded = verifyRefreshToken(refreshToken);
    
    if (!decoded) {
      return res.status(401).json({
        success: false,
        message: 'Invalid or expired refresh token'
      });
    }

    // Check if refresh token exists in database and is active
    const sessionResult = await db.query(
      `SELECT us.*, u.email, u.role, u.is_active 
       FROM user_sessions us 
       JOIN users u ON us.user_id = u.id 
       WHERE us.refresh_token = $1 AND us.is_active = true AND us.expires_at > NOW()`,
      [refreshToken]
    );

    if (sessionResult.rows.length === 0) {
      return res.status(401).json({
        success: false,
        message: 'Invalid session'
      });
    }

    const session = sessionResult.rows[0];

    // Check if user is active
    if (!session.is_active) {
      return res.status(401).json({
        success: false,
        message: 'Account is deactivated'
      });
    }

    // Generate new access token
    const newAccessToken = generateAccessToken(session.user_id, session.email, session.role);

    // Update cookie
    res.cookie('access_token', newAccessToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict',
      maxAge: 15 * 60 * 1000
    });

    res.json({
      success: true,
      accessToken: newAccessToken
    });

  } catch (error) {
    console.error('Refresh token error:', error);
    res.status(500).json({
      success: false,
      message: 'Token refresh failed'
    });
  }
};

// Get Current User Profile
const getProfile = async (req, res) => {
  try {
    const userId = req.user.id;

    const result = await db.query(
      `SELECT 
        u.id, u.email, u.username, u.first_name, u.last_name, 
        u.role, u.is_verified, u.created_at,
        up.bio, up.avatar_url, up.phone, up.birthdate, up.address
       FROM users u
       LEFT JOIN user_profiles up ON u.id = up.user_id
       WHERE u.id = $1`,
      [userId]
    );

    if (result.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }

    res.json({
      success: true,
      user: result.rows[0]
    });

  } catch (error) {
    console.error('Get profile error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to fetch profile'
    });
  }
};

// Update User Profile
const updateProfile = async (req, res) => {
  try {
    const userId = req.user.id;
    const { firstName, lastName, username, email, bio, phone, birthdate, address } = req.body;

    // Start transaction
    await db.query('BEGIN');

    // Update users table
    const userUpdateResult = await db.query(
      `UPDATE users 
       SET first_name = COALESCE($1, first_name),
           last_name = COALESCE($2, last_name),
           username = COALESCE($3, username),
           email = COALESCE($4, email),
           updated_at = CURRENT_TIMESTAMP
       WHERE id = $5
       RETURNING id, email, username, first_name, last_name, role`,
      [firstName, lastName, username, email, userId]
    );

    // Update or insert user profile
    const profileResult = await db.query(
      `INSERT INTO user_profiles (user_id, bio, phone, birthdate, address)
       VALUES ($1, $2, $3, $4, $5)
       ON CONFLICT (user_id) 
       DO UPDATE SET 
         bio = COALESCE($2, user_profiles.bio),
         phone = COALESCE($3, user_profiles.phone),
         birthdate = COALESCE($4, user_profiles.birthdate),
         address = COALESCE($5, user_profiles.address),
         updated_at = CURRENT_TIMESTAMP
       RETURNING *`,
      [userId, bio, phone, birthdate, address]
    );

    await db.query('COMMIT');

    res.json({
      success: true,
      message: 'Profile updated successfully',
      user: userUpdateResult.rows[0],
      profile: profileResult.rows[0]
    });

  } catch (error) {
    await db.query('ROLLBACK');
    console.error('Update profile error:', error);
    
    // Handle unique constraint violations
    if (error.code === '23505') {
      return res.status(409).json({
        success: false,
        message: 'Username or email already exists'
      });
    }

    res.status(500).json({
      success: false,
      message: 'Failed to update profile'
    });
  }
};

// Change Password
const changePassword = async (req, res) => {
  try {
    const userId = req.user.id;
    const { currentPassword, newPassword } = req.body;

    // Get current password hash
    const result = await db.query(
      'SELECT password_hash FROM users WHERE id = $1',
      [userId]
    );

    if (result.rows.length === 0) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }

    // Verify current password
    const isValid = await comparePassword(currentPassword, result.rows[0].password_hash);
    
    if (!isValid) {
      return res.status(401).json({
        success: false,
        message: 'Current password is incorrect'
      });
    }

    // Hash new password
    const newPasswordHash = await hashPassword(newPassword);

    // Update password
    await db.query(
      'UPDATE users SET password_hash = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
      [newPasswordHash, userId]
    );

    // Invalidate all user sessions (except current)
    const refreshToken = req.cookies.refresh_token || req.body.refreshToken;
    if (refreshToken) {
      await db.query(
        'UPDATE user_sessions SET is_active = false WHERE user_id = $1 AND refresh_token != $2',
        [userId, refreshToken]
      );
    }

    res.json({
      success: true,
      message: 'Password changed successfully'
    });

  } catch (error) {
    console.error('Change password error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to change password'
    });
  }
};

// Forgot Password (Send reset email)
const forgotPassword = async (req, res) => {
  try {
    const { email } = req.body;

    // Check if user exists
    const userResult = await db.query(
      'SELECT id, email FROM users WHERE email = $1 AND is_active = true',
      [email]
    );

    if (userResult.rows.length === 0) {
      // Don't reveal if user exists (security best practice)
      return res.json({
        success: true,
        message: 'If an account exists with this email, you will receive a password reset link'
      });
    }

    const user = userResult.rows[0];

    // Generate reset token
    const resetToken = require('crypto').randomBytes(32).toString('hex');
    const expiresAt = new Date(Date.now() + 3600000); // 1 hour from now

    // Store reset token in database
    await db.query(
      `INSERT INTO password_reset_tokens (user_id, token, expires_at) 
       VALUES ($1, $2, $3)`,
      [user.id, resetToken, expiresAt]
    );

    // In production, you would send an email here
    // For demo purposes, we'll return the token
    const resetLink = `http://localhost:3000/reset-password?token=${resetToken}`;
    
    console.log(`Password reset link for ${email}: ${resetLink}`);

    res.json({
      success: true,
      message: 'Password reset link sent to email',
      // In production, remove this line:
      resetLink: process.env.NODE_ENV === 'development' ? resetLink : undefined
    });

  } catch (error) {
    console.error('Forgot password error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to process password reset request'
    });
  }
};

// Reset Password (with token)
const resetPassword = async (req, res) => {
  try {
    const { token, password } = req.body;

    // Find valid reset token
    const tokenResult = await db.query(
      `SELECT pt.user_id, pt.expires_at 
       FROM password_reset_tokens pt
       JOIN users u ON pt.user_id = u.id
       WHERE pt.token = $1 AND pt.used = false AND pt.expires_at > NOW() AND u.is_active = true`,
      [token]
    );

    if (tokenResult.rows.length === 0) {
      return res.status(400).json({
        success: false,
        message: 'Invalid or expired reset token'
      });
    }

    const { user_id, expires_at } = tokenResult.rows[0];

    // Hash new password
    const passwordHash = await hashPassword(password);

    // Start transaction
    await db.query('BEGIN');

    // Update password
    await db.query(
      'UPDATE users SET password_hash = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
      [passwordHash, user_id]
    );

    // Mark token as used
    await db.query(
      'UPDATE password_reset_tokens SET used = true WHERE token = $1',
      [token]
    );

    // Invalidate all user sessions
    await db.query(
      'UPDATE user_sessions SET is_active = false WHERE user_id = $1',
      [user_id]
    );

    await db.query('COMMIT');

    res.json({
      success: true,
      message: 'Password reset successful. Please login with your new password.'
    });

  } catch (error) {
    await db.query('ROLLBACK');
    console.error('Reset password error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to reset password'
    });
  }
};

module.exports = {
  register,
  login,
  logout,
  refreshToken,
  getProfile,
  updateProfile,
  changePassword,
  forgotPassword,
  resetPassword
};

PART 8: AUTHENTICATION ROUTES

src/routes/authRoutes.js:

javascript
const express = require('express');
const router = express.Router();
const { validate, validationRules } = require('../middleware/validationMiddleware');
const { 
  authenticate, 
  requireVerified, 
  requireOwnershipOrAdmin 
} = require('../middleware/authMiddleware');
const {
  register,
  login,
  logout,
  refreshToken,
  getProfile,
  updateProfile,
  changePassword,
  forgotPassword,
  resetPassword
} = require('../controllers/authController');

// Public Routes
router.post('/register', validate(validationRules.register), register);
router.post('/login', validate(validationRules.login), login);
router.post('/refresh-token', refreshToken);
router.post('/forgot-password', validate(validationRules.forgotPassword), forgotPassword);
router.post('/reset-password', validate(validationRules.resetPassword), resetPassword);

// Protected Routes (require authentication)
router.post('/logout', authenticate, logout);
router.get('/profile', authenticate, getProfile);
router.put('/profile', authenticate, validate(validationRules.updateProfile), updateProfile);
router.put('/change-password', authenticate, validate(validationRules.changePassword), changePassword);

// Admin-only routes (example)
router.get('/admin/users', authenticate, requireRole('admin'), async (req, res) => {
  // This would be in a separate controller
  res.json({ message: 'Admin user list endpoint' });
});

module.exports = router;

PART 9: MAIN SERVER FILE

src/server.js:

javascript
const express = require('express');
const cookieParser = require('cookie-parser');
const cors = require('cors');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 3000;

// Import routes
const authRoutes = require('./routes/authRoutes');

// Middleware
app.use(cors({
  origin: process.env.NODE_ENV === 'development' 
    ? 'http://localhost:5173'  // React/Vite dev server
    : 'https://yourdomain.com', // Production domain
  credentials: true
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

// Logging middleware
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
  next();
});

// Routes
app.use('/api/auth', authRoutes);

// Public test route
app.get('/', (req, res) => {
  res.json({
    message: '🔐 Authentication API is running',
    version: '1.0.0',
    endpoints: {
      auth: {
        register: 'POST /api/auth/register',
        login: 'POST /api/auth/login',
        logout: 'POST /api/auth/logout',
        profile: 'GET /api/auth/profile',
        refreshToken: 'POST /api/auth/refresh-token',
        forgotPassword: 'POST /api/auth/forgot-password',
        resetPassword: 'POST /api/auth/reset-password',
        changePassword: 'PUT /api/auth/change-password'
      }
    },
    documentation: 'See README.md for detailed API docs'
  });
});

// Protected test route
app.get('/api/protected', (req, res, next) => {
  const { authenticate } = require('./middleware/authMiddleware');
  authenticate(req, res, () => {
    res.json({
      message: '✅ This is a protected route',
      user: req.user
    });
  });
});

// Health check
app.get('/health', async (req, res) => {
  try {
    const db = require('./config/database');
    const result = await db.query('SELECT NOW()');
    res.json({
      status: 'healthy',
      database: 'connected',
      timestamp: result.rows[0].now,
      uptime: process.uptime()
    });
  } catch (error) {
    res.status(500).json({
      status: 'unhealthy',
      database: 'disconnected',
      error: error.message
    });
  }
});

// 404 handler
app.use('*', (req, res) => {
  res.status(404).json({
    success: false,
    message: `Route ${req.originalUrl} not found`
  });
});

// Error handler
app.use((err, req, res, next) => {
  console.error('Server error:', err.stack);
  res.status(500).json({
    success: false,
    message: 'Internal server error',
    error: process.env.NODE_ENV === 'development' ? err.message : undefined
  });
});

// Start server
app.listen(PORT, () => {
  console.log(`🚀 Authentication server running on port ${PORT}`);
  console.log(`⚡ Environment: ${process.env.NODE_ENV}`);
  console.log(`🌐 Base URL: http://localhost:${PORT}`);
  console.log(`🔐 Authentication endpoints available at /api/auth/*`);
});

PART 10: TESTING THE AUTHENTICATION SYSTEM

Test with curl Commands:

bash
# 1. Register a new user
curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "username": "testuser",
    "password": "Password123",
    "confirmPassword": "Password123",
    "firstName": "John",
    "lastName": "Doe"
  }'

# 2. Login
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "Password123"
  }'

# Save the accessToken from response for next steps

# 3. Get profile (with token)
curl -X GET http://localhost:3000/api/auth/profile \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# 4. Refresh token
curl -X POST http://localhost:3000/api/auth/refresh-token \
  -H "Content-Type: application/json" \
  -d '{
    "refreshToken": "YOUR_REFRESH_TOKEN"
  }'

# 5. Change password
curl -X PUT http://localhost:3000/api/auth/change-password \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "currentPassword": "Password123",
    "newPassword": "NewPassword123",
    "confirmNewPassword": "NewPassword123"
  }'

# 6. Forgot password
curl -X POST http://localhost:3000/api/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com"}'

# 7. Reset password (use token from forgot password)
curl -X POST http://localhost:3000/api/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{
    "token": "RESET_TOKEN_FROM_EMAIL",
    "password": "NewPassword456",
    "confirmPassword": "NewPassword456"
  }'

# 8. Logout
curl -X POST http://localhost:3000/api/auth/logout \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Test with Postman Collection:

Create a Postman collection with these requests:

  1. Register → POST /api/auth/register

  2. Login → POST /api/auth/login → Save token as environment variable

  3. Get Profile → GET /api/auth/profile → Use token in Authorization header

  4. Refresh Token → POST /api/auth/refresh-token

  5. Logout → POST /api/auth/logout

PART 11: FRONTEND INTEGRATION EXAMPLE

Simple React Login Component:

jsx
// Login.jsx
import React, { useState } from 'react';
import axios from 'axios';

const Login = () => {
  const [formData, setFormData] = useState({
    email: '',
    password: ''
  });
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    setError('');

    try {
      const response = await axios.post(
        'http://localhost:3000/api/auth/login',
        formData,
        { withCredentials: true } // Important for cookies
      );

      // Save token to localStorage (or context/state)
      localStorage.setItem('accessToken', response.data.tokens.accessToken);
      localStorage.setItem('refreshToken', response.data.tokens.refreshToken);
      localStorage.setItem('user', JSON.stringify(response.data.user));

      alert('Login successful!');
      window.location.href = '/dashboard';
      
    } catch (err) {
      setError(err.response?.data?.message || 'Login failed');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="login-container">
      <h2>Login</h2>
      {error && <div className="error">{error}</div>}
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          placeholder="Email"
          value={formData.email}
          onChange={(e) => setFormData({...formData, email: e.target.value})}
          required
        />
        <input
          type="password"
          placeholder="Password"
          value={formData.password}
          onChange={(e) => setFormData({...formData, password: e.target.value})}
          required
        />
        <button type="submit" disabled={loading}>
          {loading ? 'Logging in...' : 'Login'}
        </button>
      </form>
    </div>
  );
};

export default Login;

Axios Interceptor for Auto Token Refresh:

javascript
// axiosConfig.js
import axios from 'axios';

const api = axios.create({
  baseURL: 'http://localhost:3000/api',
  withCredentials: true
});

let isRefreshing = false;
let failedQueue = [];

const processQueue = (error, token = null) => {
  failedQueue.forEach(prom => {
    if (error) {
      prom.reject(error);
    } else {
      prom.resolve(token);
    }
  });
  failedQueue = [];
};

api.interceptors.response.use(
  response => response,
  async error => {
    const originalRequest = error.config;

    if (error.response?.status === 401 && !originalRequest._retry) {
      if (isRefreshing) {
        return new Promise((resolve, reject) => {
          failedQueue.push({ resolve, reject });
        }).then(token => {
          originalRequest.headers['Authorization'] = 'Bearer ' + token;
          return api(originalRequest);
        }).catch(err => Promise.reject(err));
      }

      originalRequest._retry = true;
      isRefreshing = true;

      try {
        const refreshToken = localStorage.getItem('refreshToken');
        const response = await axios.post(
          'http://localhost:3000/api/auth/refresh-token',
          { refreshToken }
        );

        const newAccessToken = response.data.accessToken;
        localStorage.setItem('accessToken', newAccessToken);
        
        api.defaults.headers.common['Authorization'] = 'Bearer ' + newAccessToken;
        originalRequest.headers['Authorization'] = 'Bearer ' + newAccessToken;
        
        processQueue(null, newAccessToken);
        return api(originalRequest);
      } catch (refreshError) {
        processQueue(refreshError, null);
        localStorage.removeItem('accessToken');
        localStorage.removeItem('refreshToken');
        localStorage.removeItem('user');
        window.location.href = '/login';
        return Promise.reject(refreshError);
      } finally {
        isRefreshing = false;
      }
    }

    return Promise.reject(error);
  }
);

// Set token from localStorage on app start
const token = localStorage.getItem('accessToken');
if (token) {
  api.defaults.headers.common['Authorization'] = 'Bearer ' + token;
}

export default api;

PART 12: SECURITY BEST PRACTICES

Add to your server.js:

javascript
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');

// Security middleware
app.use(helmet());

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP, please try again later.'
});

app.use('/api/auth', limiter);

// Specific rate limiting for login/register
const authLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 5, // 5 attempts per hour
  message: 'Too many authentication attempts, please try again later.'
});

app.use('/api/auth/login', authLimiter);
app.use('/api/auth/register', authLimiter);

PART 13: DEPLOYMENT READY CONFIG

Updated .env.production:

env
NODE_ENV=production
PORT=80

# PostgreSQL (using connection string for cloud databases)
DATABASE_URL=postgresql://username:password@host:port/database

# JWT Secrets (generate strong ones!)
JWT_SECRET=your_strong_production_secret_min_32_chars_long
JWT_REFRESH_SECRET=your_strong_production_refresh_secret_min_32_chars
JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d

# Security
BCRYPT_SALT_ROUNDS=12

# CORS (your frontend domain)
CORS_ORIGIN=https://yourdomain.com

# Session
COOKIE_SECURE=true
COOKIE_SAME_SITE=none

Dockerfile for Deployment:

dockerfile
FROM node:18-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY . .

# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
USER nodejs

# Expose port
EXPOSE 3000

# Start server
CMD ["npm", "start"]

PART 14: COMMON ISSUES & SOLUTIONS

Problem: "JWT secret not defined"

Solution: Check your .env file exists and JWT_SECRET is set

Problem: Cookies not working with frontend

Solution: Configure CORS properly:

javascript
app.use(cors({
  origin: 'http://localhost:5173', // Your frontend URL
  credentials: true
}));

Problem: "password authentication failed"

Solution:

  1. Check PostgreSQL is running

  2. Verify credentials in .env

  3. Test connection: psql -U postgres -h localhost

Problem: Token expires too quickly

Solution: Adjust in .env:

env
JWT_EXPIRES_IN=30m  # 30 minutes
JWT_REFRESH_EXPIRES_IN=30d  # 30 days

PART 15: FINAL PROJECT STRUCTURE

text
auth-tutorial/
├── node_modules/
├── src/
│   ├── config/
│   │   └── database.js
│   ├── controllers/
│   │   └── authController.js
│   ├── middleware/
│   │   ├── authMiddleware.js
│   │   └── validationMiddleware.js
│   ├── routes/
│   │   └── authRoutes.js
│   ├── utils/
│   │   ├── jwtUtils.js
│   │   └── passwordUtils.js
│   └── server.js
├── .env
├── .env.example
├── .gitignore
├── database-setup.sql
├── docker-compose.yml (optional)
├── Dockerfile (optional)
├── package.json
└── README.md

QUICK START COMMANDS:

bash
# 1. Clone and setup
git clone <your-repo>
cd auth-tutorial
npm install

# 2. Set up database
psql -U postgres -f database-setup.sql

# 3. Configure environment
cp .env.example .env
# Edit .env with your settings

# 4. Start development server
npm run dev

# 5. Test endpoints
curl http://localhost:3000













































No comments:

Post a Comment

ORA-00845: MEMORY_TARGET not supported on this system

 The shared memory file system should have enough space to accommodate the MEMORY_TARGET and MEMORY_MAX_TARGET values. To verify: SQL> sh...