Hey there! If you're building APIs in Node.js, you've probably dealt with the headache of validating user inputs. This notebook walks through using Joi to handle that validation cleanly.
We'll cover validating URL parameters, query strings, headers, and request bodies - all the pieces you need to build robust APIs without writing tons of repetitive validation code. I'll show practical examples for each type of validation, including how to integrate with Express.js.
Let's get started!
$ npm install joi added 6 packages in 3s 28 packages are looking for funding run `npm fund` for details
Joi version: 17.13.3
URL parameters are common in RESTful APIs - things like /users/:userId
or /products/:productId
. Let's see how to make sure they're valid.
Validating numeric user ID: { isValid: true, error: null, params: { userId: 123 } } Validating UUID user ID: { isValid: false, error: '"userId" must be a valid GUID', params: null } Validating invalid user ID: { isValid: false, error: '"userId" must be a valid GUID', params: null }
Query parameters are those ?page=1&limit=20
things you see in URLs. They're super useful for filtering, sorting, and pagination.
Validating valid query parameters: { isValid: true, error: null, query: { page: 2, limit: 50, sort: 'price_asc', category: 'electronics', minPrice: 100, maxPrice: 500, inStock: true } }
Validating invalid query parameters: { isValid: false, error: [ '"page" must be greater than or equal to 1', '"limit" must be less than or equal to 100', '"sort" must be one of [price_asc, price_desc, newest, popular]', '"maxPrice" must be greater than ref:minPrice' ], query: null }
Validating minimal query parameters (with defaults): { isValid: true, error: null, query: { page: 1, limit: 20, sort: 'newest' } }
Headers often contain important stuff like auth tokens and content types. Validating them is crucial for security.
Validating valid headers: { isValid: true, error: null, headers: { authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', 'content-type': 'application/json', 'user-agent': 'Mozilla/5.0', 'x-api-key': 'api-key-123', 'accept-language': 'en-US' } }
Validating invalid headers: { isValid: false, error: [ '"authorization" with value "Basic dXNlcjpwYXNz" fails to match the required pattern: /^Bearer /', '"content-type" must be [application/json]' ], headers: null }
Request bodies are where the bulk of your API data usually lives. They can get pretty complex, so good validation is essential.
Validating valid user creation body: { isValid: true, error: null, body: { username: 'johndoe', email: 'john@example.com', password: 'Password123', name: { first: 'John', last: 'Doe' }, age: 30, bio: 'Software developer with 5 years of experience', roles: [ 'user', 'editor' ], settings: { newsletter: true, theme: 'light', notifications: true } } }
Validating invalid user creation body: { isValid: false, error: [ '"username" length must be at least 3 characters long', '"email" must be a valid email', 'Password must contain at least one uppercase letter, one lowercase letter, and one number', '"name.last" is required', '"age" must be greater than or equal to 13', '"roles[1]" must be one of [user, admin, editor]' ], body: null }
If you're using Express.js (and who isn't?), here's how to create reusable validation middleware.
Example Express route with Joi validation: app.post('/api/users', createValidationMiddleware(createUserSchema), (req, res) => { // Your handler code here // req.body is already validated });
Example Express route with multiple validations: app.put('/api/users/:userId', validateRequest({ params: userIdParamSchema, body: updateUserSchema, query: userQuerySchema }), (req, res) => { // Your handler code here });
Real-world APIs often need to validate complex requests with multiple components. Here's how to handle that.
Validating complex API request: { isValid: true, error: null, request: { body: { name: 'Wireless Headphones', description: 'High-quality wireless headphones with noise cancellation', price: 149.99, category: 'electronics', tags: [Array], attributes: [Object], stock: 100, images: [Array] }, headers: { authorization: 'Bearer token123', 'content-type': 'application/json', 'user-agent': 'Mozilla/5.0' }, query: { dryRun: true } } }
Don't repeat yourself! Create reusable schema components to keep your validation code DRY.
Example of reusable schema components: getUserSchema structure: { "type": "object", "keys": { "params": { "type": "object", "keys": { "userId": { "type": "alternatives", "flags": { "presence": "required" }, "matches": [ { "schema": { "type": "number", "rules": [ { "name": "integer" }, { "name": "sign", "args": { "sign": "positive" } } ] } }, { "schema": { "type": "string", "rules": [ { "name": "guid", "args": { "options": { "version": "uuidv4" } } } ] } } ] } } }, "headers": { "type": "object", "flags": { "unknown": true }, "keys": { "authorization": { "type": "string", "flags": { "presence": "required" }, "rules": [ { "name": "pattern", "args": { "regex": "/^Bearer /" } } ] } } }, "query": { "type": "object", "keys": { "page": { "type": "number", "flags": { "default": 1 }, "rules": [ { "name": "integer" }, { "name": "min", "args": { "limit": 1 } } ] }, "limit": { "type": "number", "flags": { "default": 20 }, "rules": [ { "name": "integer" }, { "name": "min", "args": { "limit": 1 } }, { "name": "max", "args": { "limit": 100 } } ] } } } } }
That's it! You now have a solid foundation for validating all kinds of API inputs with Joi. The examples here should cover most common scenarios you'll encounter when building APIs.
Remember that good validation not only prevents bugs and security issues but also provides better feedback to your API consumers. Clear error messages help developers understand what went wrong and how to fix it.
Happy coding!