mnglab
Version:
Dead simple REST micro-framework with modular routing, API key, rate limiting, full HTTP response handling, dev tools, JSON DB, file helpers, endpoint stats, and professional developer experience.
129 lines (113 loc) • 3.87 kB
JavaScript
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const { Low, JSONFile } = require('lowdb');
const path = require('path');
const fs = require('fs');
const chalk = require('chalk');
const app = express();
app.use(cors());
app.use(helmet());
app.use(express.json());
// Rate limiter
const limiter = rateLimit({ windowMs: 60_000, max: 100 });
app.use(limiter);
// Load API Keys
const apiKeyPath = path.join(__dirname, 'apikey.json');
let API_KEYS = [];
if (fs.existsSync(apiKeyPath)) {
API_KEYS = JSON.parse(fs.readFileSync(apiKeyPath));
}
function apiKeyGuard(req, res, next) {
if (!API_KEYS.length) return next();
const key = req.headers['x-api-key'];
if (!API_KEYS.includes(key)) {
return res.status(401).json({ success: false, message: 'Invalid API key' });
}
next();
}
app.use(apiKeyGuard);
// In-memory route counter
let routeCount = 0;
function register(method, path, opts, handler) {
if (typeof opts === 'function') {
handler = opts;
opts = {};
}
const validation = Array.isArray(opts?.body) ? opts.body : null;
app[method](path, async (req, res, next) => {
if (validation) {
const missing = validation.filter((f) => req.body?.[f] === undefined);
if (missing.length) {
return res.status(400).json({ success: false, message: `Missing fields: ${missing.join(', ')}` });
}
}
try {
await handler(req, res, next);
} catch (err) {
res.status(500).json({ success: false, message: 'Internal Server Error', error: err.message });
}
});
routeCount++;
}
// Route helpers
const route = {
get: (path, opts, fn) => register('get', path, opts, fn),
post: (path, opts, fn) => register('post', path, opts, fn),
put: (path, opts, fn) => register('put', path, opts, fn),
delete: (path, opts, fn) => register('delete', path, opts, fn)
};
// Load DB
const dbFile = path.join(process.cwd(), 'db.json');
if (!fs.existsSync(dbFile)) fs.writeFileSync(dbFile, JSON.stringify({}));
const adapter = new JSONFile(dbFile);
const db = new Low(adapter);
// Utility Tools
const tools = {
log: (...args) => console.log(chalk.green('[LOG]'), ...args),
error: (...args) => console.log(chalk.red('[ERR]'), ...args),
readFile: (file) => fs.readFileSync(path.join(__dirname, file), 'utf-8'),
writeFile: (file, content) => fs.writeFileSync(path.join(__dirname, file), content),
appendFile: (file, content) => fs.appendFileSync(path.join(__dirname, file), content),
deleteFile: (file) => fs.unlinkSync(path.join(__dirname, file)),
timestamp: () => new Date().toISOString(),
jsonResponse: (res, success, message, data = null) => {
res.json({ success, message, data });
},
countRoutes: () => routeCount,
};
// Dynamic route loader
const routesDir = path.join(__dirname, 'routes');
if (fs.existsSync(routesDir)) {
const files = fs.readdirSync(routesDir).filter(f => f.endsWith('.js'));
files.forEach(file => {
const registerRoute = require(path.join(routesDir, file));
registerRoute(route, db, tools);
});
}
// Home route
route.get('/', (req, res) => {
tools.jsonResponse(res, true, 'MNGBotz API Ready', {
routes: tools.countRoutes(),
timestamp: tools.timestamp()
});
});
// 404 Middleware
app.use((req, res) => {
res.status(404).json({ success: false, message: 'Endpoint not found' });
});
// Error Middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ success: false, message: 'Something went wrong', error: err.message });
});
// Start Server
const PORT = process.env.PORT || 3000;
(async () => {
await db.read();
app.listen(PORT, () => {
console.log(chalk.blue(`✅ Server running on port ${PORT}`));
console.log(chalk.magenta(`📡 ${routeCount} routes registered.`));
});
})();