Back to Tutorial Overview
Node.js API Development - Complete Guide
Build production-ready REST APIs with Node.js, Express, and MongoDB
Section 1: Project Setup
Initialize Your Node.js Project
Let's start by creating a new Node.js project with Express:
# Create project directory
mkdir node-api-tutorial
cd node-api-tutorial
# Initialize npm project
npm init -y
# Install dependencies
npm install express mongoose dotenv cors helmet
npm install --save-dev nodemon typescript @types/node @types/express
# Initialize TypeScript
npx tsc --initProject Structure
node-api-tutorial/
├── src/
│ ├── config/
│ │ └── database.ts
│ ├── models/
│ │ └── User.ts
│ ├── routes/
│ │ └── userRoutes.ts
│ ├── controllers/
│ │ └── userController.ts
│ ├── middleware/
│ │ ├── auth.ts
│ │ └── errorHandler.ts
│ └── server.ts
├── .env
├── .gitignore
├── package.json
└── tsconfig.jsonSection 2: Express Server Setup
Basic Server Configuration
// src/server.ts
import express, { Application, Request, Response } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import dotenv from 'dotenv';
import connectDB from './config/database';
dotenv.config();
const app: Application = express();
const PORT = process.env.PORT || 5000;
// Middleware
app.use(helmet()); // Security headers
app.use(cors()); // Enable CORS
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true }));
// Connect to database
connectDB();
// Health check route
app.get('/health', (req: Request, res: Response) => {
res.status(200).json({
status: 'success',
message: 'API is running!',
timestamp: new Date().toISOString()
});
});
// Start server
app.listen(PORT, () => {
console.log(`🚀 Server running on port ${PORT}`);
});
export default app;Section 3: MongoDB Database Setup
Database Connection
// src/config/database.ts
import mongoose from 'mongoose';
const connectDB = async (): Promise<void> => {
try {
const mongoURI = process.env.MONGODB_URI || 'mongodb://localhost:27017/nodeapi';
await mongoose.connect(mongoURI);
console.log('✅ MongoDB connected successfully');
mongoose.connection.on('error', (err) => {
console.error('MongoDB connection error:', err);
});
mongoose.connection.on('disconnected', () => {
console.log('MongoDB disconnected');
});
} catch (error) {
console.error('❌ MongoDB connection failed:', error);
process.exit(1);
}
};
export default connectDB;Environment Variables
# .env
PORT=5000
MONGODB_URI=mongodb://localhost:27017/nodeapi
JWT_SECRET=your_jwt_secret_key_here
NODE_ENV=developmentSection 4: Creating Data Models
User Model with Mongoose
// src/models/User.ts
import mongoose, { Document, Schema } from 'mongoose';
import bcrypt from 'bcryptjs';
export interface IUser extends Document {
name: string;
email: string;
password: string;
role: 'user' | 'admin';
createdAt: Date;
updatedAt: Date;
comparePassword(candidatePassword: string): Promise<boolean>;
}
const userSchema = new Schema<IUser>(
{
name: {
type: String,
required: [true, 'Name is required'],
trim: true,
minlength: [2, 'Name must be at least 2 characters'],
maxlength: [50, 'Name cannot exceed 50 characters']
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
lowercase: true,
trim: true,
match: [/^\S+@\S+\.\S+$/, 'Please enter a valid email']
},
password: {
type: String,
required: [true, 'Password is required'],
minlength: [6, 'Password must be at least 6 characters'],
select: false // Don't return password by default
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
}
},
{
timestamps: true
}
);
// Hash password before saving
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
// Method to compare passwords
userSchema.methods.comparePassword = async function (
candidatePassword: string
): Promise<boolean> {
return bcrypt.compare(candidatePassword, this.password);
};
export default mongoose.model<IUser>('User', userSchema);Section 5: API Controllers
User Controller with CRUD Operations
// src/controllers/userController.ts
import { Request, Response } from 'express';
import User from '../models/User';
import jwt from 'jsonwebtoken';
// Register new user
export const register = async (req: Request, res: Response) => {
try {
const { name, email, password } = req.body;
// Check if user exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({
success: false,
message: 'User already exists'
});
}
// Create user
const user = await User.create({ name, email, password });
// Generate JWT token
const token = jwt.sign(
{ id: user._id, email: user.email },
process.env.JWT_SECRET!,
{ expiresIn: '7d' }
);
res.status(201).json({
success: true,
message: 'User registered successfully',
data: {
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role
},
token
}
});
} catch (error: any) {
res.status(500).json({
success: false,
message: 'Registration failed',
error: error.message
});
}
};
// Login user
export const login = async (req: Request, res: Response) => {
try {
const { email, password } = req.body;
// Find user with password field
const user = await User.findOne({ email }).select('+password');
if (!user) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
// Check password
const isPasswordValid = await user.comparePassword(password);
if (!isPasswordValid) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
// Generate token
const token = jwt.sign(
{ id: user._id, email: user.email },
process.env.JWT_SECRET!,
{ expiresIn: '7d' }
);
res.status(200).json({
success: true,
message: 'Login successful',
data: {
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role
},
token
}
});
} catch (error: any) {
res.status(500).json({
success: false,
message: 'Login failed',
error: error.message
});
}
};
// Get all users (admin only)
export const getAllUsers = async (req: Request, res: Response) => {
try {
const users = await User.find().select('-password');
res.status(200).json({
success: true,
count: users.length,
data: users
});
} catch (error: any) {
res.status(500).json({
success: false,
message: 'Failed to fetch users',
error: error.message
});
}
};
// Get single user
export const getUserById = async (req: Request, res: Response) => {
try {
const user = await User.findById(req.params.id).select('-password');
if (!user) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
res.status(200).json({
success: true,
data: user
});
} catch (error: any) {
res.status(500).json({
success: false,
message: 'Failed to fetch user',
error: error.message
});
}
};
// Update user
export const updateUser = async (req: Request, res: Response) => {
try {
const { name, email } = req.body;
const user = await User.findByIdAndUpdate(
req.params.id,
{ name, email },
{ new: true, runValidators: true }
).select('-password');
if (!user) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
res.status(200).json({
success: true,
message: 'User updated successfully',
data: user
});
} catch (error: any) {
res.status(500).json({
success: false,
message: 'Failed to update user',
error: error.message
});
}
};
// Delete user
export const deleteUser = async (req: Request, res: Response) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
res.status(200).json({
success: true,
message: 'User deleted successfully'
});
} catch (error: any) {
res.status(500).json({
success: false,
message: 'Failed to delete user',
error: error.message
});
}
};Section 6: Authentication & Authorization
JWT Authentication Middleware
// src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
interface JwtPayload {
id: string;
email: string;
}
declare global {
namespace Express {
interface Request {
user?: JwtPayload;
}
}
}
export const authenticate = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
// Get token from header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
success: false,
message: 'No token provided'
});
}
const token = authHeader.split(' ')[1];
// Verify token
const decoded = jwt.verify(
token,
process.env.JWT_SECRET!
) as JwtPayload;
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({
success: false,
message: 'Invalid or expired token'
});
}
};
export const authorize = (...roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({
success: false,
message: 'Unauthorized'
});
}
// Check if user role is authorized
// In real app, get role from database
// For now, assume it's in the token
next();
};
};Section 7: API Routes
User Routes Configuration
// src/routes/userRoutes.ts
import express from 'express';
import {
register,
login,
getAllUsers,
getUserById,
updateUser,
deleteUser
} from '../controllers/userController';
import { authenticate } from '../middleware/auth';
const router = express.Router();
// Public routes
router.post('/register', register);
router.post('/login', login);
// Protected routes
router.get('/', authenticate, getAllUsers);
router.get('/:id', authenticate, getUserById);
router.put('/:id', authenticate, updateUser);
router.delete('/:id', authenticate, deleteUser);
export default router;Register Routes in Server
// src/server.ts (add this)
import userRoutes from './routes/userRoutes';
// ... existing middleware
// API Routes
app.use('/api/v1/users', userRoutes);
// ... rest of server codeSection 8: Testing Your API
Example API Requests
# Register new user
curl -X POST http://localhost:5000/api/v1/users/register \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"email": "john@example.com",
"password": "password123"
}'
# Login
curl -X POST http://localhost:5000/api/v1/users/login \
-H "Content-Type: application/json" \
-d '{
"email": "john@example.com",
"password": "password123"
}'
# Get all users (with authentication)
curl -X GET http://localhost:5000/api/v1/users \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Get user by ID
curl -X GET http://localhost:5000/api/v1/users/USER_ID \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Update user
curl -X PUT http://localhost:5000/api/v1/users/USER_ID \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "John Updated",
"email": "john.updated@example.com"
}'
# Delete user
curl -X DELETE http://localhost:5000/api/v1/users/USER_ID \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Congratulations! 🎉
You've built a production-ready Node.js REST API with authentication, validation, and error handling!
Back to Tutorial Overview