secure and scalable advanced-level Node.js API scaffold using Express, JWT authentication, MongoDB, and best practices like rate limiting, input validation, and security headers.

 

Tech Stack

  • Express.js

  • MongoDB + Mongoose

  • JWT for auth

  • bcrypt for password hashing

  • express-validator for validation

  • helmet for headers

  • dotenv for environment config

  • express-rate-limit for rate limiting


1. Project Structure


pwc-secure-api/ ├── config/ │ └── db.js ├── controllers/ │ └── auth.controller.js ├── middlewares/ │ ├── auth.middleware.js │ └── error.middleware.js ├── models/ │ └── user.model.js ├── routes/ │ └── auth.routes.js ├── utils/ │ └── generateToken.js ├── .env ├── app.js ├── server.js ├── package.json

2. Install Dependencies

bash

npm init -y npm install express mongoose dotenv bcryptjs jsonwebtoken express-validator helmet express-rate-limit cors

3. Sample Files

.env

env

PORT=5000 MONGO_URI=mongodb://localhost:27017/secure-api JWT_SECRET=supersecretkey

config/db.js

js

const mongoose = require('mongoose'); const connectDB = async () => { try { await mongoose.connect(process.env.MONGO_URI); console.log('MongoDB connected'); } catch (error) { console.error('DB connection failed', error); process.exit(1); } }; module.exports = connectDB;

models/user.model.js

js

const mongoose = require('mongoose'); const bcrypt = require('bcryptjs'); const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true }, password: { type: String, required: true } }); // Hash password before save userSchema.pre('save', async function (next) { if (!this.isModified('password')) return next(); this.password = await bcrypt.hash(this.password, 10); next(); }); module.exports = mongoose.model('User', userSchema);

utils/generateToken.js

js

const jwt = require('jsonwebtoken'); const generateToken = (userId) => { return jwt.sign({ id: userId }, process.env.JWT_SECRET, { expiresIn: '1h' }); }; module.exports = generateToken;

middlewares/auth.middleware.js

js

const jwt = require('jsonwebtoken'); const protect = (req, res, next) => { const token = req.headers.authorization?.split(" ")[1]; if (!token) return res.status(401).json({ message: "Not authorized" }); try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded.id; next(); } catch { res.status(401).json({ message: "Invalid token" }); } }; module.exports = { protect };

controllers/auth.controller.js

js

const User = require('../models/user.model'); const bcrypt = require('bcryptjs'); const generateToken = require('../utils/generateToken'); exports.register = async (req, res) => { const { email, password } = req.body; const exists = await User.findOne({ email }); if (exists) return res.status(400).json({ message: "User exists" }); const user = await User.create({ email, password }); res.status(201).json({ token: generateToken(user._id) }); }; exports.login = async (req, res) => { const { email, password } = req.body; const user = await User.findOne({ email }); if (!user || !(await bcrypt.compare(password, user.password))) { return res.status(400).json({ message: "Invalid credentials" }); } res.json({ token: generateToken(user._id) }); }; exports.profile = async (req, res) => { const user = await User.findById(req.user).select("-password"); res.json(user); };

routes/auth.routes.js

js

const express = require('express'); const { register, login, profile } = require('../controllers/auth.controller'); const { body } = require('express-validator'); const { protect } = require('../middlewares/auth.middleware'); const router = express.Router(); router.post('/register', body('email').isEmail(), body('password').isLength({ min: 6 }), register ); router.post('/login', login); router.get('/profile', protect, profile); module.exports = router;

app.js

js

const express = require('express'); const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const cors = require('cors'); const authRoutes = require('./routes/auth.routes'); const { validationResult } = require('express-validator'); const app = express(); app.use(helmet()); app.use(cors()); app.use(express.json()); // Rate limiting app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 })); // Validation error handler app.use((req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() }); next(); }); app.use('/api/auth', authRoutes); module.exports = app;

server.js

js

require('dotenv').config(); const app = require('./app'); const connectDB = require('./config/db'); connectDB(); const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Want to Add More?

I can help you add:

  • Refresh tokens

  • 2FA with OTP

  • Roles/Permissions

  • Swagger API docs

  • Unit testing with Jest or Mocha


No comments:

Post a Comment

Best for you