REST APIs serve as the backbone of modern web applications, enabling seamless communication between different services. Node.js provides an excellent platform for building these APIs due to its event-driven architecture and extensive ecosystem. This tutorial demonstrates how to create a secure REST API using Node.js with JSON Web Tokens (JWT) for authentication.

JWT authentication offers several advantages over traditional session-based approaches. JWTs are stateless, self-contained tokens that carry user information within the token itself. This eliminates the need for server-side session storage, making your API more scalable and suitable for distributed systems.

Project Setup and Dependencies

Start by creating a new Node.js project and installing the required dependencies:

mkdir jwt-api-tutorial && cd jwt-api-tutorial
npm init -y
npm install express jsonwebtoken bcryptjs dotenv

Create a .env file to store your JWT secret key securely:

JWT_SECRET=your_super_secret_key_here
PORT=3000

The dependencies include Express for the web framework, jsonwebtoken for JWT handling, bcryptjs for password hashing, and dotenv for environment variable management.

Basic Server Configuration

Create an index.js file with the following server setup:

require(\'dotenv\').config();
const express = require(\'express\');
const jwt = require(\'jsonwebtoken\');
const bcrypt = require(\'bcryptjs\');

const app = express();
app.use(express.json());

// In-memory user storage (use a database in production)
let users = [];

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(
Server running on port ${PORT}
); });

User Registration and Authentication

Implement user registration with password hashing for security:

// User registration endpoint
app.post(\'/api/register\', async (req, res) => {
  try {
    const { username, email, password } = req.body;
    
    // Check if user already exists
    const existingUser = users.find(u => u.username === username || u.email === email);
    if (existingUser) {
      return res.status(400).json({ error: \'User already exists\' });
    }
    
    // Hash password
    const saltRounds = 12;
    const hashedPassword = await bcrypt.hash(password, saltRounds);
    
    // Create new user
    const newUser = {
      id: users.length + 1,
      username,
      email,
      password: hashedPassword,
      createdAt: new Date().toISOString()
    };
    
    users.push(newUser);
    
    res.status(201).json({ 
      message: \'User registered successfully\',
      userId: newUser.id 
    });
  } catch (error) {
    res.status(500).json({ error: \'Internal server error\' });
  }
});

Create a login endpoint that validates credentials and returns a JWT:

// User login endpoint
app.post(\'/api/login\', async (req, res) => {
  try {
    const { username, password } = req.body;
    
    // Find user
    const user = users.find(u => u.username === username);
    if (!user) {
      return res.status(401).json({ error: \'Invalid credentials\' });
    }
    
    // Verify password
    const isValidPassword = await bcrypt.compare(password, user.password);
    if (!isValidPassword) {
      return res.status(401).json({ error: \'Invalid credentials\' });
    }
    
    // Generate JWT token
    const token = jwt.sign(
      { 
        userId: user.id,
        username: user.username 
      },
      process.env.JWT_SECRET,
      { expiresIn: \'24h\' }
    );
    
    res.json({ 
      token,
      user: {
        id: user.id,
        username: user.username,
        email: user.email
      }
    });
  } catch (error) {
    res.status(500).json({ error: \'Internal server error\' });
  }
});

JWT Middleware for Route Protection

Create middleware to authenticate and authorize requests using JWT tokens:

// JWT authentication middleware
const authenticateJWT = (req, res, next) => {
  const authHeader = req.headers.authorization;
  
  if (!authHeader) {
    return res.status(401).json({ error: \'Access token required\' });
  }
  
  const token = authHeader.split(\' \')[1]; // Bearer TOKEN
  
  if (!token) {
    return res.status(401).json({ error: \'Invalid token format\' });
  }
  
  jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
    if (err) {
      return res.status(403).json({ error: \'Invalid or expired token\' });
    }
    
    req.user = decoded;
    next();
  });
};

Protected Routes Implementation

Apply the authentication middleware to protect sensitive endpoints:

// Protected route example
app.get(\'/api/profile\', authenticateJWT, (req, res) => {
  const user = users.find(u => u.id === req.user.userId);
  
  if (!user) {
    return res.status(404).json({ error: \'User not found\' });
  }
  
  res.json({
    id: user.id,
    username: user.username,
    email: user.email,
    createdAt: user.createdAt
  });
});

// Another protected route
app.put(\'/api/profile\', authenticateJWT, async (req, res) => {
  try {
    const { email } = req.body;
    const userIndex = users.findIndex(u => u.id === req.user.userId);
    
    if (userIndex === -1) {
      return res.status(404).json({ error: \'User not found\' });
    }
    
    users[userIndex].email = email;
    
    res.json({ 
      message: \'Profile updated successfully\',
      user: {
        id: users[userIndex].id,
        username: users[userIndex].username,
        email: users[userIndex].email
      }
    });
  } catch (error) {
    res.status(500).json({ error: \'Internal server error\' });
  }
});

Error Handling and Security Best Practices

Implement comprehensive error handling and security measures:

// Global error handler
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: \'Something went wrong!\' });
});

// Handle 404 routes
app.use(\'*\', (req, res) => {
  res.status(404).json({ error: \'Route not found\' });
});

For production applications, consider implementing rate limiting, CORS policies, and using a proper database instead of in-memory storage. Security measures like HTTPS enforcement and input validation are essential for protecting user data.

Testing Your API

Test your API endpoints using tools like Postman or curl commands:

Register a new user

curl -X POST http://localhost:3000/api/register \\ -H "Content-Type: application/json" \\ -d \'{"username":"testuser","email":"test@example.com","password":"password123"}\'

Login and get token

curl -X POST http://localhost:3000/api/login \\ -H "Content-Type: application/json" \\ -d \'{"username":"testuser","password":"password123"}\'

Access protected route

curl -X GET http://localhost:3000/api/profile \\ -H "Authorization: Bearer YOUR_JWT_TOKEN_HERE"

This JWT-based authentication system provides a solid foundation for modern web development projects. The stateless nature of JWTs makes them ideal for microservices architectures and APIs that need to scale across multiple servers. For enhanced security in production environments, consider implementing additional measures like VPN protection for sensitive API endpoints.

Remember to store your JWT secrets securely, implement proper token expiration, and consider using refresh tokens for long-lived applications. Regular security audits and dependency updates help maintain the integrity of your authentication system.