3 · Connect, test and build · 18 MIN
Validate a query parameter at the boundary
Treat URL values as untrusted strings before using them.
A query parameter is always text, even when it looks like a number. Number conversion alone accepts surprising values such as empty text. First define a permitted syntax, then check numeric bounds. This example validates limit before slicing a fixed list. Validation is not authorization: it only establishes that the value fits an input contract. Returning 400 for malformed input makes the failure actionable for the client and keeps invalid data out of later logic.
Read raw as text, then inspect Number(raw). Test a normal value and each invalid value in the URL. Use searchParams.get for q and validate its length. Apply filter first and slice second.
Before you start
Install Node.js 22.12+ (or a supported newer LTS). Create a folder named codingneed-node. Open a terminal in that folder. Save ONE lesson example as lesson.mjs and run node lesson.mjs. No npm dependencies are required. HTTP lessons keep running until you press Ctrl+C; stop the previous server before starting another.
File for this example: lesson.mjs
New words, explained
- queryParameter
- A named value following ? in a URL.
- boundary
- The point where outside data enters your application.
- contract
- The accepted input and promised output of an operation.
Follow the example step by step
- Read raw as text, then inspect Number(raw).
- Test a normal value and each invalid value in the URL.
- Use searchParams.get for q and validate its length.
- Apply filter first and slice second.
You are ready to move on when: Missing limit uses 3. Zero, decimals and text produce 400. Search filters before the result limit is applied.
Read the example
import {createServer} from 'node:http';
const topics = ['React', 'SQL', 'Node.js'];
createServer((req, res) => {
const url = new URL(req.url, 'http://localhost');
res.setHeader('Content-Type', 'application/json');
if (req.method !== 'GET' || url.pathname !== '/topics') { res.writeHead(404); res.end('{}'); return; }
const raw = url.searchParams.get('limit') ?? '3';
const limit = Number(raw);
if (!/^[1-9][0-9]*$/.test(raw) || !Number.isSafeInteger(limit) || limit > 3) {
res.writeHead(400); res.end(JSON.stringify({error:'limit must be an integer from 1 to 3'})); return;
}
res.end(JSON.stringify(topics.slice(0, limit)));
}).listen(3000, '127.0.0.1', () => console.log('Open http://127.0.0.1:3000/topics?limit=2'));Check the expected output
/topics?limit=2 returns ["React","SQL"]. limit=0, limit=abc and limit=2.5 each return 400.
Your challenge
Add an optional q search parameter that filters titles case-insensitively before applying limit. Limit q to 40 characters.
Solution cost: Discuss the operations in this small example; rendering and I/O costs depend on the host. time · Proportional to the example’s retained data. space
Common trap
Validation cannot be replaced by escaping text or by trusting the browser form.
Study the project implementation
import {createServer} from 'node:http';
const topics = ['React', 'SQL', 'Node.js'];
createServer((req, res) => {
const url = new URL(req.url, 'http://localhost');
res.setHeader('Content-Type', 'application/json');
if (req.method !== 'GET' || url.pathname !== '/topics') { res.writeHead(404); res.end('{}'); return; }
const raw = url.searchParams.get('limit') ?? '3';
const limit = Number(raw);
if (!/^[1-9][0-9]*$/.test(raw) || !Number.isSafeInteger(limit) || limit > 3) {
res.writeHead(400); res.end(JSON.stringify({error:'limit must be an integer from 1 to 3'})); return;
}
res.end(JSON.stringify(topics.slice(0, limit)));
}).listen(3000, '127.0.0.1', () => console.log('Open http://127.0.0.1:3000/topics?limit=2'));Further reading: Official documentation
Next lesson: Write repeatable tests for the logic →