UNPKG

namune

Version:

A Quick Setup Express API.

571 lines (431 loc) 14.8 kB
# namune [![npm version](https://img.shields.io/npm/v/namune)](https://www.npmjs.com/package/namune) [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) **A production-ready Express.js library with built-in authentication, middleware configuration, and route management.** --- ## Features - 🔐 Built-in authentication system (register, login, logout, verification) - ⚙️ Configurable middleware stack (CORS, sessions, body parsing) - 🗃️ MongoDB integration with Mongoose - 🛣️ Automatic route loading and management - 🔒 Passport.js integration for local authentication - 🚀 Production-ready error handling and security practices - 🧩 Modular architecture for easy extension - 🖂 Mail Sending - < > Advanced Pagination - 💳 Payments with payfast - 📁 Cloud File Uploads (To AWS S3) --- ## Installation ```bash npm install -g namune # or yarn global add namune ``` --- ## Quick Start ```bash nmn create # or nmn create myproject ``` ```bash # For hot reload when you save changes npm run dev # or for manual reload npm start ``` --- ## API Reference ### Middleware Configuration (`MidsConfigs`) #### `new MidsConfigs(app)` - `app` (Express): Express application instance #### Methods ##### `registerMiddlewares(options)` Registers all middleware. ```javascript options = { dbConfig: { database_name: "myapp" }, usePassportLogin: true, passportConfig: { // Add your seperate custom local strategies for login authentication to the list // Make sure to seperate strategyName with one dash (e.g user-local) strategyList: [ { strategyName: "user-local", model: sharedDependencies.models.User, // Make sure this is included in shared.deps.js file usernameField: "email", // Change relevant logic in routes/auth/api.routes.js to match your username field verifyAccount: (user) => { if (!user.accountActive) return "Please verify your email first."; return null; }, }, ], }, }; ``` ### Authentication Middleware #### `ensureAuthenticated(req, res, next)` Middleware that checks if a user is authenticated. Returns 401 if not. ### Authentication Routes (Can't be overwritten) - `POST /v1/auth/register` - User registration - `POST /v1/auth/verify/account` - Account verification - `POST /v1/auth/login` - User login - `GET /v1/auth/logout` - User logout - `POST /v1/auth/verify-email` - Email verification for password reset - `POST /v1/auth/reset-password` - Password reset - `POST /v1/auth/delete-account` - Account deletion --- ### Hooks ##### AUTH Hooks: Returns a response Response Structure: {success: boolean, message: string, data: any} ##### `onSuccessRegister` ##### `onFailRegister` ##### `onSuccessVerifyUser` ##### `onFailVerifyUser` ##### `onSuccessLogin` ##### `onFailLogin` ##### `onSuccessLogout` ##### `onFailLogout` ##### `onSuccessDeleteUser` ##### `onFailDeleteUser` ##### `onSuccessVerify` ##### `onFailVerify` ##### `onSuccessChangePassword` ##### `onFailChangePassword` Example ```javascript // deps.js module.exports = { global: {}, models: {}, utils: {}, hooks: { onSuccessRegister: yourCustomFunction, // Fires when user successfully registers (/auth/register) onFailRegister: yourCustomFunction, // Fires when user fails to register (/auth/register) }, }; ``` ## Route Management Routes are automatically loaded from the `routes` directory. Use this pattern: ```javascript // routes/example/api.routes.js class ExampleRoute { constructor(router, dependencies) { this.router = router; this.dependencies = dependencies; } exampleHandler(req, res) { res.json({ message: "Success" }); } registerRoutes() { this.router.get("/", this.exampleHandler); } } module.exports = ExampleRoute; ``` --- ## Configuration ### Environment Variables ##### GENERAL | Variable | Default | Description | | --------------- | ------------------------- | ---------------------------------------- | | `PORT` | 8000 | Server port | | `MONGODB_URI` | mongodb://localhost:27017 | MongoDB connection string | | `DATABASE_NAME` | example-db | Database name | | `NODE_ENV` | development | Runtime environment | | `STAGING_ENV` | undefined | Runtime environment | | `STAGING_URL` | undefined | URL for your staging client/frontend | | `PROD_URL` | undefined | URL for your production client/frontend | | `DEV_URL` | http://localhost:3000 | URL for your development client/frontend | ##### PAYMENTS (Using Payfast) | Variable | Default | Description | | ------------------------------------ | ------------------------ | ------------------------------------- | | `NGROK_SERVER_URL` | http://localhost:8000/v1 | ngrok server url | | `NGROK_CLIENT_URL` | http://localhost:3000 | ngrok client url | | `PAYFAST_PASS_PHRASE` | undefined | Payfast Pass Phrase | | `PAYFAST_MERCHANT_ID` | 10000100 | Payfast Merchant ID | | `PAYFAST_MERCHANT_KEY` | 46f0cd694581a | Payfast Merchant Key | | `PROD_API_URL` | undefined | Production Api URL | | `PAYFAST_PAYMENT_CONFIRM_EMAIL` | undefined | Email to send payment confirmation to | | `process.env.PAYFAST_PAYMENT_METHOD` | cc | Payfast Payment method | ### Middleware Options You can configure: - Database name - Passport.js integration - Session storage - CORS settings - Body parser limits --- ## Error Handling - Authentication failures return `401` - Database errors return `500` - Invalid requests return `400` - Route not found returns `404` - JSON responses include `success` and `message` fields - Detailed errors in `development` environment --- ## Examples ### Custom Route Implementation ```javascript // routes/custom/api.routes.js const { Router } = require("express"); class CustomRoute { constructor(router, dependencies) { this.router = router; this.dependencies = dependencies; } getData(req, res) { res.json({ data: "Protected resource", user: req.user }); } registerRoutes() { const { authenticate } = this.dependencies.global; this.router.get("/data", authenticate, this.getData); } } module.exports = CustomRoute; ``` ### Generating Hash (for any string) ```javascript // namune/utils/gen-hash.js const genHash = require("namune/utils/gen-hash.js"); const hash = genHash({ password: "12345" }); ``` ### Send an email (Uses nodemailer) ```javascript // namune/utils/sendMail.js const sendMail = require("namune/utils/sendMail.js"); // Optional: Transport options. (Below is the dafault setup) const transportOptions = { host: "mail.smtp2go.com", port: 80, // 25, 587, and 8025 can also be used (or 2525). }; // IMPRTANT: // Make sure you have USER and PASS in the .env file for authenticating your email for transport // Options const from = "fromexample@gmail.co"; const to = "toexample@gmail.co"; const subject = "Verify account"; const subject = "Verify account"; const attachments = [ { filename: "emailBanner.png", path: process.cwd() + "/public/img/emailBanner.png", cid: "emailBanner@domiher.com", }, ]; const html = "<h1> Hi, please verify your account </h1>"; const mailOptions = { from, to, subject, attachments, html, }; sendMail(mailOptions); ``` ### Lazy-Load Dependencies to avoid circular loop errors ```javascript module.exports = { global: {}, models: {}, utils: { get myUtil() { return require("./utils/myUtil"); }, }, hooks: { get onFailRegister() { return require("./hooks/register.fail"); }, }, }; ``` ### Pagination ##### Input ```javascript // For non-routes const paginate = require('namune/utils/paginate'); // For routes (api.routes.js) // const paginate = this.dependencies.utils.paginate const data = [ { user: { name: "Alice" }, score: 91 }, { user: { name: "Charlie" }, score: 82 }, { user: { name: "Bob" }, score: 75 }, ]; // Sort by nested property `user.name` const result = paginate(data, { page: 1, limit: 2, sortBy: 'user.name', order: 'asc' filterFn: (item) => item.score > 50 }); console.log(result); ``` ##### ✅ Output Sample ```bash { data: [ { user: { name: "Alice" }, score: 91 }, { user: { name: "Bob" }, score: 75 } ], paginationMeta: { page: 1, limit: 2, totalItems: 3, totalPages: 2, hasNext: true, hasPrevious: false, nextPage: 2, previousPage: null } } ``` ###### Options `page: Number` Page to go to - Optional (Default is 0) `limit: Number` Number of items - Optional (Default is 10) `sortBy: String` Sort the data. Allows for nested properties - Optional (Default is null) `order` Order the Items by "asc" or "desc" - Optional (Default is "asc") `filterFn` Filter Function to filter data - Optional (Default is null) ### Upload Files to cloud (Uses AWS S3) ```javascript // Non routes const { uploadBase64ImageToS3 } = require("namune/utils/cloudFileUpload"); // For routes (api.routes.js) // const {uploadBase64ImageToS3} = this.dependencies.utils.cloudFileUpload let base64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA..."; // A base64 image uploadBase64ImageToS3(base64).then(console.log); ``` ##### Options (Second Parameter after base64) `ACL: String` For Access Control - Optional (Default is "public-read") `appendKey: String` Folder in bucket - Optional (Default is "image-upload") `region: String` Your S3 bucket region - Optional (Default is "eu-north-1") #### Other functions (supports multipart/form-data) `uploadImage` `uploadVideo` `uploadAudio` Example ```javascript const multer = require("multer"); const upload = multer(); // memoryStorage by default // ... Class logic for routes registerRoutes(){ this.router.post("/upload/image", upload.single("file"), async (req, res) => { const result = await this.dependencies.utils.cloudFileUpload.uploadImage(req.file); res.json(result); }); this.router.post("/upload/video", upload.single("file"), async (req, res) => { const result = await this.dependencies.utils.cloudFileUpload.uploadVideo(req.file); res.json(result); }); this.router.post("/upload/audio", upload.single("file"), async (req, res) => { const result = await this.dependencies.utils.cloudFileUpload.uploadAudio(req.file); res.json(result); }); } ``` ##### Options for the above functions: `buffer: Buffer` - File buffer `originalName: String` - Original file name (e.g. image.jpg) `folder: String` - Folder in bucket (e.g. images, audio, videos) `options: {ACL: String, ContentType: String}` - Optional: ACL, ContentType #### Delete uploaded files `deleteFile` - Expects a file param `deleteMultipleFiles` - Expects an array of AWS keys (e.g. ['images/image.jpg', 'images/image2.jpg']) `deleteAllFilesInFolder` - Expects a prefix (e.g. "images/user123/") ##### Options `file: {Key: String, VersionId: any, Location: String}` - File object uploaded. versionId is optional `keys: String[]` - Array of Keys Example ```js // Delete specific files await deleteMultipleFiles(["images/abc.jpg", "videos/test.mp4"]); // Delete everything under a folder await deleteAllFilesInFolder("images/user123/"); ``` ### Payments Using Payfast Example ```js // In Routes context // Making the payment signature this.router.get("/order", (req, res) => { const payfast = this.dependencies.utils.payfast; const { data, html } = payfast.sig({ firstname: order.customerFirstName, // Required lastname: order.customerLastName, // Required email: order.customerEmail, // Required payment_id: `${order.orderNumber}`, // Required amount: `${order.totalPrice}`, // Required order_num: `#${order.orderNumber}`, // Required phone: order.customerPhoneNumber, // Optional address: order.customerAddress, // Optional }); return res.status(200).json({ success: true, message: "Successful!", data: { data, html }, }); /* Data returned from payfast.sig: merchant_id, merchant_key, return_url, cancel_url, notify_url, name_first, name_last, email_address, m_payment_id, amount, item_name, email_confirmation, confirmation_address, payment_method, signature */ }); // Request from Payfast after cancellation or successful payment this.router.post("/notify", async (req, res) => { const { m_payment_id, amount_gross } = await req.body; const payfast = this.dependencies.utils.payfast; // Notify/Confirmation payfast.confirm(req, amount_gross); return res.status(200).json({ success: true, message: "Successful!", data: null, }); }); ``` ### Minimal Working Example ```javascript // server.js require("dotenv").config(); const express = require("express"); const app = express(); const http = require("http").createServer(app); const MidsConfigs = require("namune/config/mids-configs"); const mids = new MidsConfigs(app); mids.registerMiddlewares({ dbConfig: { database_name: "myapp" }, usePassportLogin: true, passportConfig: { userModel: require("./models/User"), usernameField: "email", }, }); app.use("/api", require("namune/routes/api")); const PORT = process.env.PORT || 8000; http.listen(PORT, () => console.log(`Server running on port ${PORT}`)); ``` --- ## Contributing Contributions are welcome! 1. Fork the repository 2. Create your feature branch: `git checkout -b feature/your-feature` 3. Commit your changes: `git commit -am 'Add some feature'` 4. Push to the branch: `git push origin feature/your-feature` 5. Open a pull request --- ## License This project is licensed under the [MIT License](LICENSE).