Testing with API Clients
While building LearnHub you will send hundreds of requests. API clients — Postman, Insomnia, Thunder Client, VS Code REST Client and curl — make that fast, and their collections and scripts turn manual checks into repeatable tests.
On this page · 7 min · Advanced
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 7 min
- Difficulty
- Advanced
- Updated
- Oct 28, 2026
Related tutorials
Learning objectives#
- Send requests with curl and a GUI client
- Use environments and variables for base URLs and tokens
- Chain requests (login → use the token)
- Turn a collection into an automated smoke test
Explanation#
curl is everywhere: curl -i -X POST -H "Content-Type: application/json" -d "{…}" http://localhost:3000/api/courses (-i shows status and headers).
Postman / Insomnia / Thunder Client organise requests into collections with environments (local, staging) holding variables like {{baseUrl}} and {{token}}. Test scripts check status and body after each request.
VS Code REST Client uses plain .http files committed to the repo: requests separated by ###, variables with @name, and responses of named requests reusable later — great for code review.
Chain requests: the login response’s accessToken is stored in a variable and sent as Authorization: Bearer {{token}} by the following requests.
Automate it: Postman collections run in CI with Newman; or write the same flow as a short script (below) that exits with a non-zero code on failure. Use it as a smoke test after each deployment.
Syntax#
# learnhub.http — VS Code REST Client
@baseUrl = http://localhost:3000
### Login
# @name login
POST {{baseUrl}}/api/auth/login
Content-Type: application/json
{ "email": "ravi@example.com", "password": "learn-node-2026" }
### Create a course with the token from the login response
POST {{baseUrl}}/api/courses
Authorization: Bearer {{login.response.body.accessToken}}
Content-Type: application/json
{ "title": "Testing APIs", "price": 299 }
Code example: A collection as code: chained requests with assertions#
import express from 'express';
// ── The API under test (normally your real app, already running) ──
const app = express();
app.use(express.json());
const courses = [];
app.post('/api/auth/login', (req, res) =>
req.body.password === 'learn-node-2026' ? res.json({ accessToken: 'demo-token-123' }) : res.status(401).json({ error: 'Invalid email or password' }));
app.post('/api/courses', (req, res) => {
if (req.get('authorization') !== 'Bearer demo-token-123') return res.status(401).json({ error: 'Authentication required' });
const course = { id: courses.length + 1, ...req.body };
courses.push(course);
res.status(201).location(`/api/courses/${course.id}`).json(course);
});
app.get('/api/courses/:id', (req, res) => {
const course = courses.find(c => c.id === Number(req.params.id));
course ? res.json(course) : res.status(404).json({ error: 'Not found' });
});
// ── The "collection": environment, requests and tests ──
const env = { baseUrl: 'http://localhost:3116' }; // like a Postman environment
let passed = 0, failed = 0;
const expect = (name, condition) => {
condition ? passed++ : failed++;
console.log(` ${condition ? '✓' : '✗'} ${name}`);
};
async function send(name, method, path, { body, auth } = {}) {
console.log(`${method} ${path} (${name})`);
const res = await fetch(env.baseUrl + path, {
method,
headers: { 'Content-Type': 'application/json', ...(auth && { Authorization: `Bearer ${env.token}` }) },
body: body && JSON.stringify(body),
});
return { status: res.status, headers: res.headers, json: await res.json() };
}
const server = app.listen(3116, async () => {
let r = await send('login', 'POST', '/api/auth/login', { body: { email: 'ravi@example.com', password: 'learn-node-2026' } });
expect('status is 200', r.status === 200);
expect('returns an access token', typeof r.json.accessToken === 'string');
env.token = r.json.accessToken; // like pm.environment.set('token', …)
r = await send('create course', 'POST', '/api/courses', { auth: true, body: { title: 'Testing APIs', price: 299 } });
expect('status is 201', r.status === 201);
expect('Location points to the new course', r.headers.get('location') === `/api/courses/${r.json.id}`);
env.courseId = r.json.id;
r = await send('read it back', 'GET', `/api/courses/${env.courseId}`);
expect('title round-trips', r.json.title === 'Testing APIs');
r = await send('create without token', 'POST', '/api/courses', { body: { title: 'Nope' } });
expect('status is 401', r.status === 401);
console.log(`${passed} passed, ${failed} failed`);
process.exitCode = failed ? 1 : 0; // CI fails if any check fails
server.close();
});
POST /api/auth/login (login) ✓ status is 200 ✓ returns an access token POST /api/courses (create course) ✓ status is 201 ✓ Location points to the new course GET /api/courses/1 (read it back) ✓ title round-trips POST /api/courses (create without token) ✓ status is 401 6 passed, 0 failed
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- env plays the role of a Postman environment: base URL plus variables filled in as the run proceeds.
- The login response’s token is saved and used by later requests — request chaining.
- Each request is followed by tests on status, headers and body.
- A negative test (no token → 401) checks the security rule, not just the happy path.
- The exit code makes the script usable as a CI or post-deployment smoke test.
Real-world use case#
After every deployment of LearnHub, the pipeline runs the smoke-test collection against the new environment. If login or course creation fails, the deployment is rolled back automatically.
Common mistakes#
Requests that contain http://localhost:3000 and a pasted token break on every environment and expire. Use variables.
Manual checks are forgotten under pressure. Save them as a collection or script and run them automatically.
Exported Postman environments often contain real tokens and passwords. Keep secrets in local, uncommitted environments.
Best practices#
- Collections or .http files in the repo
- Environments for base URLs and secrets
- Chain login → token → requests
- Run the collection in CI (Newman or a script)
Remember this#
curl -i shows status + headers.
Variables: {{baseUrl}}, {{token}} — never hard-code.
A collection with tests = a smoke test.
Practice exercise#
Your task: Write curl commands that log in, store the token in a shell variable, and create a course with it.
Need a hint?
Show solution
TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"ravi@example.com","password":"learn-node-2026"}' | jq -r .accessToken)
curl -i -X POST http://localhost:3000/api/courses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"title":"Testing APIs","price":299}'
Mini quiz#
What does curl’s -i flag do?
Want more? Practise all Node.js REST API MCQs · Node.js REST API interview questions
My notes#
Saved in this browser.