UNPKG

qmemory

Version:

A comprehensive production-ready Node.js utility library with MongoDB document operations, user ownership enforcement, Express.js HTTP utilities, environment-aware logging, and in-memory storage. Features 96%+ test coverage with comprehensive error handli

262 lines (232 loc) 10.9 kB
/** * QMemory Library Demo Application * Demonstrates core functionality with a simple Express.js API * * This demo application showcases the practical usage of the qmemory library * in a real Express.js server environment. It provides a complete REST API * for user management operations using the library's utilities. * * Design rationale: * - Demonstrates library integration patterns for real applications * - Shows proper error handling using library HTTP utilities * - Provides working examples of all major library features * - Serves as both documentation and functional testing platform * * Architecture decisions: * - Uses in-memory storage for simplicity and quick demonstration * - Implements comprehensive logging for debugging and monitoring * - Follows REST conventions for intuitive API design * - Includes health checks for production readiness demonstration */ const express = require('express'); const { MemStorage, sendNotFound, sendInternalServerError, ensureMongoDB } = require('./index'); const app = express(); const port = process.env.PORT || 5000; // Local helper functions provide minimal implementations since the library // no longer exports these utilities function logInfo(...args) { console.log('[INFO]', ...args); } // unify info logging function logError(...args) { console.error('[ERROR]', ...args); } // unify error logging function sendSuccess(res, message, data) { // send standard 200 response console.log(`sendSuccess is running with ${message}`); // trace call for debugging try { const payload = { message, timestamp: new Date().toISOString() }; if (data !== undefined) payload.data = data; // include optional data res.status(200).json(payload); console.log(`sendSuccess is returning ${JSON.stringify(payload)}`); // confirm output } catch (error) { console.error('sendSuccess failed', error); // log failure path } } function sendBadRequest(res, message) { // send standard 400 response console.log(`sendBadRequest is running with ${message}`); // trace call for debugging try { const payload = { message, timestamp: new Date().toISOString() }; res.status(400).json(payload); console.log('sendBadRequest has run resulting in a final value of 400'); // confirm completion } catch (error) { console.error('sendBadRequest failed', error); // log failure path } } function sanitizeInput(str) { // remove spaces and html tags to mitigate xss console.log(`sanitizeInput is running with ${str}`); // trace sanitizer use try { const value = typeof str === 'string' ? str.trim().replace(/<[^>]*>/g, '') : ''; console.log(`sanitizeInput is returning ${value}`); // confirm sanitized result return value; } catch (error) { console.error('sanitizeInput failed', error); // log sanitizer errors return ''; } } // Middleware app.use(express.json()); // body parser for JSON payloads, ensures consistent req.body app.use(express.static('public')); // serve static files for documentation and example assets // Initialize storage const storage = new MemStorage(); // in-memory store is used to keep the demo self contained // Logging middleware app.use((req, res, next) => { const start = Date.now(); // capture start time to calculate response duration res.on('finish', () => { const duration = Date.now() - start; // measure request processing time logInfo(`${req.method} ${req.url} - ${res.statusCode} - ${duration}ms`); // log concise request details for monitoring }); next(); }); // Health check endpoint app.get('/health', async (req, res) => { // returns service status to support uptime monitoring try { const userCount = (await storage.getAllUsers()).length; // await to get accurate user total const health = { status: 'healthy', // simple status message used by monitoring tools uptime: process.uptime(), // uptime info provides quick readiness metric memory: process.memoryUsage(), // snapshot of memory usage for debugging userCount, // confirm storage interaction timestamp: new Date().toISOString() }; sendSuccess(res, 'Service is healthy', health); // send standardized success } catch (error) { logError('Health check failed', error); // log for operator visibility sendInternalServerError(res, 'Health check failed'); } }); // Demo API endpoints app.get('/', (req, res) => { // basic API index for manual exploration res.json({ title: 'QMemory Library Demo', description: 'Production-ready Node.js utility library demonstration', endpoints: { 'GET /health': 'Health check and system status', 'GET /users': 'List all users', 'POST /users': 'Create new user (JSON: {username, displayName})', // updated to reflect display name usage 'GET /users/:id': 'Get user by ID', 'DELETE /users/:id': 'Delete user by ID', 'POST /users/clear': 'Clear all users (development only)' }, examples: { createUser: { method: 'POST', url: '/users', body: { username: 'johndoe', displayName: 'John Doe' } // changed example payload field } } }); }); // User management endpoints app.get('/users', async (req, res) => { // list all stored users for testing purposes try { const users = await storage.getAllUsers(); // await promise to get user array reliably sendSuccess(res, `Found ${users.length} users`, users); } catch (error) { logError('Failed to fetch users', error); // log ensures visibility in dev sendInternalServerError(res, 'Failed to fetch users'); } }); app.post('/users', async (req, res) => { // create a user for demo operations try { const { username, displayName } = req.body; // get required username and optional display name const safeName = sanitizeInput(username); // sanitize inputs to prevent XSS and maintain consistent storage const safeDisplay = sanitizeInput(displayName); // sanitize optional display name field if (!safeName) { // ensure sanitized username exists return sendBadRequest(res, 'Username is required and must be a string'); } const user = await storage.createUser({ username: safeName, displayName: safeDisplay }); // pass only fields used by storage logInfo(`Created user: ${safeName}`); // record creation event for auditing sendSuccess(res, 'User created successfully', user); } catch (error) { if (error.message.includes('already exists')) { sendBadRequest(res, error.message); // duplicate user results in 400 } else { logError('Failed to create user', error); // other errors flagged as 500 sendInternalServerError(res, 'Failed to create user'); } } }); app.get('/users/:id', async (req, res) => { // fetch a single user by id try { const id = parseInt(req.params.id, 10); // parse as base-10 integer for clarity if (!Number.isInteger(id) || !/^\d+$/.test(req.params.id)) { // validate numeric input strictly return sendBadRequest(res, 'User ID must be numeric'); // reject non-numeric IDs with 400 } const user = await storage.getUser(id); // await user fetch to ensure data if (!user) { // handle unknown id return sendNotFound(res, 'User not found'); } sendSuccess(res, 'User found', user); } catch (error) { logError('Failed to fetch user', error); // log before sending generic error sendInternalServerError(res, 'Failed to fetch user'); } }); app.delete('/users/:id', async (req, res) => { // remove a user by id try { const id = parseInt(req.params.id, 10); // parse as base-10 integer for consistency if (!Number.isInteger(id) || !/^\d+$/.test(req.params.id)) { // enforce numeric id format return sendBadRequest(res, 'User ID must be numeric'); // reject invalid ids with 400 } const deleted = await storage.deleteUser(id); // await deletion result if (!deleted) { // handle missing user return sendNotFound(res, 'User not found'); } logInfo(`Deleted user with ID: ${id}`); // audit deletion event sendSuccess(res, 'User deleted successfully'); } catch (error) { logError('Failed to delete user', error); // preserve stack for debugging sendInternalServerError(res, 'Failed to delete user'); } }); app.post('/users/clear', async (req, res) => { // wipe storage when testing if (process.env.NODE_ENV === 'production') { return sendBadRequest(res, 'Clear operation not allowed in production'); // protect production data } try { await storage.clear(); // await to ensure cleanup completes logInfo('Cleared all users'); // log maintenance activity sendSuccess(res, 'All users cleared successfully'); } catch (error) { logError('Failed to clear users', error); // log for debugging sendInternalServerError(res, 'Failed to clear users'); } }); // Error handling middleware app.use((error, req, res, next) => { logError('Unhandled error:', error); // capture unexpected issues if (res.headersSent) { // delegate to default handler if headers already sent return next(error); } sendInternalServerError(res, 'An unexpected error occurred'); // hide error specifics from clients }); // 404 handler app.use((req, res) => { sendNotFound(res, 'Endpoint not found'); // unified not-found response }); // Graceful shutdown process.on('SIGTERM', () => { // capture container shutdown event for graceful exit logInfo('SIGTERM received, shutting down gracefully'); // log exit request for operators if (server) { // only close when server was started to prevent undefined errors server.close(() => { // close server to release port before process exits logInfo('Server closed'); // confirm server has been closed for reliability process.exit(0); // exit after server shutdown }); } else { // handle case where server was never started (e.g. in tests) process.exit(0); // exit immediately without server cleanup } }); let server; // holds HTTP server instance when started manually or via CLI if (require.main === module) { // start server only when running this file directly server = app.listen(port, '0.0.0.0', () => { // bind to all interfaces for demo usage logInfo(`QMemory Demo App listening on port ${port}`); // log startup details for monitoring logInfo('Environment:', process.env.NODE_ENV || 'development'); // log running mode for clarity // Create some sample data in development if (process.env.NODE_ENV !== 'production') { // avoid polluting production DB storage.createUser({ username: 'demo', displayName: 'Demo User' }) // sample uses displayName to match route .then(() => logInfo('Created demo user')) .catch(err => logError('Failed to create demo user:', err)); } }); } module.exports = { app, server }; // export server for tests and app for external usage