Testing APIs is a crucial part of modern web development. In this guide, you’ll build a robust Node.js API and use Insomnia to validate every key scenario—from happy paths to edge cases—so your service stays reliable in production.
What you’ll learn
- Set up a production-ready Node.js API with Express
- Implement structured error handling and validation
- Test all CRUD operations using Insomnia
- Organize and automate your API tests
- Follow best practices for API development and quality
Prerequisites
Before diving in, make sure you have:
- Node.js (v14 or higher) installed
- A basic understanding of JavaScript and REST APIs
- A code editor (VS Code recommended)
Part 1: Build your Node.js API
1.1 Project setup
Start by creating a clean project structure and installing dependencies:
mkdir user-management-api
cd user-management-api
npm init -y
npm install express
npm install --save-dev nodemon
nodemon restarts the server automatically whenever you save changes, which speeds up local development.
1.2 Recommended project structure
user-management-api/
├── index.js
├── package.json
└── README.md
1.3 Configure package.json
Update the scripts section so you can run the API in both production and development modes:
{
"name": "user-management-api",
"version": "1.0.0",
"description": "Simple REST API for user management",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"keywords": ["api", "express", "rest"],
"author": "Your Name",
"license": "MIT"
}
1.4 Build the Express API
Create index.js with a fully working set of CRUD endpoints, validation, and production-ready error handling:
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// In-memory database (replace with a real database in production)
let users = [
{ id: 1, name: 'John Doe', email: '[email protected]', role: 'admin' },
{ id: 2, name: 'Jane Smith', email: '[email protected]', role: 'user' }
];
// Helper function to generate new IDs
const generateId = () => {
return users.length > 0 ? Math.max(...users.map((u) => u.id)) + 1 : 1;
};
// Validation middleware
const validateUser = (req, res, next) => {
const { name, email } = req.body;
if (!name || name.trim().length === 0) {
return res.status(400).json({
error: 'Validation failed',
message: 'Name is required and cannot be empty'
});
}
if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
return res.status(400).json({
error: 'Validation failed',
message: 'Invalid email format'
});
}
next();
};
// Routes
app.get('/health', (req, res) => {
res.json({ status: 'OK', timestamp: new Date().toISOString() });
});
app.get('/api/users', (req, res) => {
res.json({
success: true,
count: users.length,
data: users
});
});
app.get('/api/users/:id', (req, res) => {
const userId = parseInt(req.params.id, 10);
const user = users.find((u) => u.id === userId);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found',
message: `No user found with ID: ${userId}`
});
}
res.json({
success: true,
data: user
});
});
app.post('/api/users', validateUser, (req, res) => {
const { name, email, role } = req.body;
if (email && users.some((u) => u.email === email)) {
return res.status(409).json({
success: false,
error: 'Conflict',
message: 'User with this email already exists'
});
}
const newUser = {
id: generateId(),
name: name.trim(),
email: email || null,
role: role || 'user'
};
users.push(newUser);
res.status(201).json({
success: true,
message: 'User created successfully',
data: newUser
});
});
app.put('/api/users/:id', validateUser, (req, res) => {
const userId = parseInt(req.params.id, 10);
const user = users.find((u) => u.id === userId);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found',
message: `No user found with ID: ${userId}`
});
}
const { name, email, role } = req.body;
if (email && email !== user.email && users.some((u) => u.email === email)) {
return res.status(409).json({
success: false,
error: 'Conflict',
message: 'Another user with this email already exists'
});
}
user.name = name.trim();
if (email) user.email = email;
if (role) user.role = role;
res.json({
success: true,
message: 'User updated successfully',
data: user
});
});
app.patch('/api/users/:id', (req, res) => {
const userId = parseInt(req.params.id, 10);
const user = users.find((u) => u.id === userId);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found',
message: `No user found with ID: ${userId}`
});
}
const { name, email, role } = req.body;
if (name !== undefined) user.name = name.trim();
if (email !== undefined) user.email = email;
if (role !== undefined) user.role = role;
res.json({
success: true,
message: 'User updated successfully',
data: user
});
});
app.delete('/api/users/:id', (req, res) => {
const userId = parseInt(req.params.id, 10);
const userIndex = users.findIndex((u) => u.id === userId);
if (userIndex === -1) {
return res.status(404).json({
success: false,
error: 'User not found',
message: `No user found with ID: ${userId}`
});
}
users.splice(userIndex, 1);
res.status(204).send();
});
app.use((req, res) => {
res.status(404).json({
success: false,
error: 'Not Found',
message: `Cannot ${req.method} ${req.path}`
});
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
success: false,
error: 'Internal Server Error',
message: 'Something went wrong on the server'
});
});
app.listen(PORT, () => {
console.log(`🚀 Server running on http://localhost:${PORT}`);
console.log(`📝 API Documentation: http://localhost:${PORT}/api/users`);
});
1.5 Start the server
Run the development server and confirm it starts without errors:
npm run dev
Expected terminal output:
🚀 Server running on http://localhost:3000
📝 API Documentation: http://localhost:3000/api/users
Part 2: Set up Insomnia
2.1 Download and install
- Visit insomnia.rest.
- Download the installer for your operating system.
- Follow the prompts and launch Insomnia.
2.2 Create your first collection
- Open Insomnia and click
Create → Request Collection. - Name it User Management API.
- Click Create to save the collection.
Part 3: Test your API endpoints
3.1 Health check
- Create a request named Health Check.
- Method:
GET - URL:
http://localhost:3000/health
{
"status": "OK",
"timestamp": "2025-10-11T10:30:00.000Z"
}
3.2 Get all users
- Request name: Get All Users
- Method:
GET - URL:
http://localhost:3000/api/users
{
"success": true,
"count": 2,
"data": [
{
"id": 1,
"name": "John Doe",
"email": "[email protected]",
"role": "admin"
},
{
"id": 2,
"name": "Jane Smith",
"email": "[email protected]",
"role": "user"
}
]
}
3.3 Get a single user
- Request name: Get User by ID
- Method:
GET - URL:
http://localhost:3000/api/users/1
{
"success": true,
"data": {
"id": 1,
"name": "John Doe",
"email": "[email protected]",
"role": "admin"
}
}
3.4 Create a user
- Request name: Create User
- Method:
POST - URL:
http://localhost:3000/api/users - Body (
JSON):
{
"name": "Alice Johnson",
"email": "[email protected]",
"role": "user"
}
Expected response (201 Created):
{
"success": true,
"message": "User created successfully",
"data": {
"id": 3,
"name": "Alice Johnson",
"email": "[email protected]",
"role": "user"
}
}
3.5 Update a user
- Request name: Update User
- Method:
PUT - URL:
http://localhost:3000/api/users/3 - Body (
JSON):
{
"name": "Alice Williams",
"email": "[email protected]",
"role": "admin"
}
{
"success": true,
"message": "User updated successfully",
"data": {
"id": 3,
"name": "Alice Williams",
"email": "[email protected]",
"role": "admin"
}
}
3.6 Partial update
- Request name: Partial Update User
- Method:
PATCH - URL:
http://localhost:3000/api/users/3 - Body (
JSON):
{
"role": "moderator"
}
This updates only the role field and leaves other attributes untouched.
3.7 Delete a user
- Request name: Delete User
- Method:
DELETE - URL:
http://localhost:3000/api/users/3
Expect a 204 No Content response with an empty body.
3.8 Exercise error scenarios
- 404 — User Not Found
Method:GET
URL:http://localhost:3000/api/users/999
{
"success": false,
"error": "User not found",
"message": "No user found with ID: 999"
}
- 400 — Validation Error
Method:POST
URL:http://localhost:3000/api/users
{
"name": "",
"email": "invalid-email"
}
Expected response:
{
"error": "Validation failed",
"message": "Name is required and cannot be empty"
}
- 409 — Duplicate Email
Method:POST
URL:http://localhost:3000/api/users
{
"name": "Duplicate User",
"email": "[email protected]"
}
{
"success": false,
"error": "Conflict",
"message": "User with this email already exists"
}
Part 4: Advanced Insomnia features
4.1 Organize requests with folders
- Right-click the collection and select New Folder.
- Create folders such as User Operations, Error Testing, and Authentication.
- Drag and drop requests into the relevant folders to keep things tidy.
4.2 Use environment variables
- Click the environment dropdown and select Manage Environments.
- Create an environment named Development with values:
{
"base_url": "http://localhost:3000",
"api_version": "api"
}
- Update requests to use
{{ _.base_url }}/{{ _.api_version }}/usersso you can switch environments with one change.
4.3 Chain requests
Leverage one response inside another request:
- Send a
POSTrequest to create a user. - Reference the returned ID in another request using
{{ _.user_id }}. - Right-click the field → Response → Body Attribute → select the previous response.
4.4 Export and share collections
Right-click the collection, choose Export, select Insomnia v4 (JSON), and share the file with teammates so everyone uses the same test suite.
4.5 Add automated tests (Insomnia Pro)
If you have Insomnia Pro, add lightweight JavaScript assertions:
const response = await insomnia.send();
expect(response.status).to.equal(200);
expect(response.data).to.have.property('success', true);
expect(response.data.data).to.be.an('array');
Part 5: Best practices
API development
- Use accurate HTTP status codes such as
200,201,204,400,404, and500. - Keep response payloads consistent so clients can parse them reliably.
- Validate user input on every write endpoint.
- Introduce API versioning (e.g.,
/api/v1/) to future-proof changes. - Handle errors gracefully and return actionable messages.
- Add structured logging with libraries such as
winstonormorgan.
Testing strategy
- Cover every endpoint, not just the happy path.
- Stress-test edge cases like empty strings, invalid formats, and large payloads.
- Validate negative scenarios: missing fields, invalid IDs, duplicate records.
- Group related requests into folders for clarity.
- Use environment variables to swap between dev, staging, and production.
- Document each request so teammates know the intent.
- Save frequently used requests as templates for new projects.
Part 6: Next steps
Enhance your API
- Replace the in-memory store with MongoDB, PostgreSQL, or MySQL.
- Implement authentication with JWT or OAuth.
- Add rate limiting to prevent abuse.
- Introduce pagination, filtering, and sorting for large datasets.
- Write automated tests with Jest or Mocha.
- Generate API documentation using Swagger/OpenAPI.
- Deploy to platforms like Heroku, Railway, or Vercel.
Explore alternative tools
- Postman for team collaboration features.
- Thunder Client as a lightweight VS Code extension.
- REST Client to run
.httpfiles directly in VS Code. - cURL for quick command-line checks.
- HTTPie for a friendlier CLI experience.
Conclusion
You now have a hardened Node.js API and a fully exercised Insomnia collection. With consistent testing, thoughtful organization, and automation in place, you can deliver reliable APIs that stay maintainable as your product grows.




