UNPKG

veloxapi

Version:

An ultra-fast, zero-dependency Node.js web framework built entirely with Node.js built-in modules

485 lines (378 loc) 10.8 kB
# Body Parsing - Handle POST/PUT Data ## Overview VeloxAPI provides **lazy body parsing** - it only parses the request body when you explicitly request it. This gives you: - ⚡ **Better Performance** - No wasted CPU on unused bodies - 🎯 **Memory Efficiency** - Parse only what you need - 📦 **Multiple Formats** - JSON, XML, YAML, form-data, multipart ## The Lazy Parsing Concept **Traditional frameworks** parse the body for every request: ```javascript // Express with body-parser app.use(express.json()); // Parses ALL JSON bodies app.post('/webhook', (req, res) => { // req.body is already parsed (wasted CPU if unused) res.send('ok'); }); ``` **VeloxAPI** parses only when needed: ```javascript router.post('/webhook', async (res, req) => { // Body NOT parsed yet (zero overhead) res.sendText('ok'); }); router.post('/users', async (res, req) => { // Parse NOW (only when needed) const body = await req.getBody(); res.sendJSON({ user: body }); }); ``` ## JSON Parsing The most common format for APIs: ```javascript router.post('/api/users', async (res, req) => { const body = await req.getBody(); // body is automatically parsed from JSON console.log(body.name); // "Alice" console.log(body.age); // 30 res.status(201).sendJSON({ created: true, user: body }); }); ``` **Request:** ```bash curl -X POST http://localhost:3000/api/users \ -H "Content-Type: application/json" \ -d '{"name":"Alice","age":30}' ``` **Response:** ```json { "created": true, "user": {"name":"Alice","age":30} } ``` ## Form Data (URL-encoded) Handle HTML form submissions: ```javascript router.post('/contact', async (res, req) => { const body = await req.getBody(); // Parsed from application/x-www-form-urlencoded console.log(body.name); // "John" console.log(body.email); // "john@example.com" console.log(body.message); // "Hello!" res.sendJSON({ received: true, data: body }); }); ``` **HTML Form:** ```html <form action="/contact" method="POST"> <input name="name" value="John"> <input name="email" value="john@example.com"> <textarea name="message">Hello!</textarea> <button type="submit">Send</button> </form> ``` **Or with curl:** ```bash curl -X POST http://localhost:3000/contact \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "name=John&email=john@example.com&message=Hello" ``` ## XML Parsing VeloxAPI automatically parses XML: ```javascript router.post('/api/xml', async (res, req) => { const body = await req.getBody(); // Parsed from XML to JavaScript object console.log(body.user.name); // "Alice" console.log(body.user.age); // "30" res.sendJSON({ parsed: body }); }); ``` **Request:** ```bash curl -X POST http://localhost:3000/api/xml \ -H "Content-Type: application/xml" \ -d '<user><name>Alice</name><age>30</age></user>' ``` **Response:** ```json { "parsed": { "name": "Alice", "age": "30" } } ``` ## YAML Parsing Parse YAML configuration: ```javascript router.post('/config', async (res, req) => { const body = await req.getBody(); // Parsed from YAML console.log(body.server.port); // 8080 console.log(body.server.host); // "0.0.0.0" res.sendJSON({ config: body }); }); ``` **Request:** ```bash curl -X POST http://localhost:3000/config \ -H "Content-Type: application/x-yaml" \ -d 'server: port: 8080 host: 0.0.0.0' ``` ## Multipart File Uploads Handle file uploads with multipart/form-data: ```javascript router.post('/upload', async (res, req) => { const body = await req.getBody(); // body.files contains uploaded files for (const file of body.files) { console.log('File:', file.filename); console.log('Size:', file.data.length, 'bytes'); console.log('Type:', file.contentType); // Save file await saveFile(file.filename, file.data); } // body.fields contains text fields console.log('Title:', body.fields.title); res.sendJSON({ uploaded: body.files.length, files: body.files.map(f => f.filename) }); }); ``` **HTML Form:** ```html <form action="/upload" method="POST" enctype="multipart/form-data"> <input name="title" value="My Upload"> <input type="file" name="file" multiple> <button type="submit">Upload</button> </form> ``` **Or with curl:** ```bash curl -X POST http://localhost:3000/upload \ -F "title=My Upload" \ -F "file=@photo.jpg" \ -F "file=@document.pdf" ``` ## Request Size Limits Protect against huge payloads: ```javascript router.post('/api/data', async (res, req) => { try { // Default limit: 10MB const body = await req.getBody(); res.sendJSON({ data: body }); } catch (err) { if (err.message.includes('too large')) { res.sendError('Request body too large (max 10MB)', 413); } else { res.sendError('Invalid request', 400); } } }); ``` **Large requests are automatically rejected:** ``` Request body exceeds limit (10485760 bytes) ``` ## Streaming Large Files For very large files, use streaming instead of buffering: ```javascript import { createWriteStream } from 'fs'; router.post('/upload-large', async (res, req) => { const writeStream = createWriteStream('./uploads/large-file.bin'); // Stream directly to disk (no memory buffering) req.req.pipe(writeStream); writeStream.on('finish', () => { res.sendJSON({ uploaded: true }); }); writeStream.on('error', (err) => { res.sendError('Upload failed', 500); }); }); ``` ## Handling Different Content Types Check content type before parsing: ```javascript router.post('/flexible', async (res, req) => { const contentType = req.getHeader('content-type'); if (contentType.includes('application/json')) { const body = await req.getBody(); res.sendJSON({ type: 'json', data: body }); } else if (contentType.includes('application/xml')) { const body = await req.getBody(); res.sendJSON({ type: 'xml', data: body }); } else { res.sendError('Unsupported content type', 415); } }); ``` ## Validation After Parsing Validate the parsed data: ```javascript router.post('/api/users', async (res, req) => { const body = await req.getBody(); // Validate required fields if (!body.name || !body.email) { return res.sendError('Missing required fields', 400); } // Validate email format if (!body.email.includes('@')) { return res.sendError('Invalid email', 400); } // Validate age range if (body.age < 18 || body.age > 120) { return res.sendError('Invalid age', 400); } // All good, create user const user = await createUser(body); res.status(201).sendJSON({ user }); }); ``` ## Raw Body Access Get the raw body buffer: ```javascript router.post('/webhook', async (res, req) => { const rawBody = await req.getRawBody(); // rawBody is a Buffer console.log('Size:', rawBody.length); console.log('Content:', rawBody.toString()); res.sendText('Received'); }); ``` ## Query + Body Together Combine query parameters and body: ```javascript router.post('/api/items', async (res, req, query) => { const body = await req.getBody(); const item = { ...body, category: query.category, // From URL: ?category=electronics featured: query.featured === 'true' }; await saveItem(item); res.status(201).sendJSON({ item }); }); ``` **Request:** ```bash curl -X POST "http://localhost:3000/api/items?category=electronics&featured=true" \ -H "Content-Type: application/json" \ -d '{"name":"Laptop","price":999}' ``` ## Error Handling Handle parsing errors gracefully: ```javascript router.post('/api/data', async (res, req) => { try { const body = await req.getBody(); res.sendJSON({ success: true, data: body }); } catch (err) { console.error('Parse error:', err); if (err instanceof SyntaxError) { res.sendError('Invalid JSON', 400); } else if (err.message.includes('too large')) { res.sendError('Payload too large', 413); } else { res.sendError('Bad request', 400); } } }); ``` ## Real-World Example: Blog API ```javascript import { VeloxServer, VeloxRouter } from 'veloxapi'; import { writeFile } from 'fs/promises'; import { join } from 'path'; const router = new VeloxRouter(); const posts = []; // Create post (JSON) router.post('/api/posts', async (res, req) => { try { const body = await req.getBody(); // Validate if (!body.title || !body.content) { return res.sendError('Title and content required', 400); } // Create post const post = { id: posts.length + 1, title: body.title, content: body.content, author: body.author || 'Anonymous', createdAt: new Date().toISOString() }; posts.push(post); res.status(201).sendJSON({ post }); } catch (err) { res.sendError('Invalid request', 400); } }); // Upload post image router.post('/api/posts/:id=number/image', async (res, req, query, params) => { try { const body = await req.getBody(); if (!body.files || body.files.length === 0) { return res.sendError('No image uploaded', 400); } const image = body.files[0]; const filename = `post-${params.id}-${image.filename}`; const filepath = join('./uploads', filename); await writeFile(filepath, image.data); res.sendJSON({ uploaded: true, url: `/uploads/${filename}` }); } catch (err) { res.sendError('Upload failed', 500); } }); // Import posts (XML) router.post('/api/posts/import', async (res, req) => { try { const body = await req.getBody(); // XML structure: <posts><post><title>...</title><content>...</content></post></posts> const importedPosts = Array.isArray(body.post) ? body.post : [body.post]; for (const xmlPost of importedPosts) { posts.push({ id: posts.length + 1, title: xmlPost.title, content: xmlPost.content, author: xmlPost.author || 'Imported', createdAt: new Date().toISOString() }); } res.sendJSON({ imported: importedPosts.length, total: posts.length }); } catch (err) { res.sendError('Import failed', 400); } }); const server = new VeloxServer() .setPort(3000) .setRouter(router) .start(); ``` ## Summary VeloxAPI body parsing: - ✅ **Lazy parsing** - Only when needed - ✅ **Multiple formats** - JSON, XML, YAML, form-data, multipart - ✅ **Automatic detection** - Based on Content-Type - ✅ **Size limits** - Protect against huge payloads - ✅ **Streaming support** - For large files - ✅ **Error handling** - Graceful failure **Next:** Learn about [Middleware](04-middleware.md) for authentication, logging, and more!