DEV Community

Dev Cookies
Dev Cookies

Posted on

Part 2: Node Js Backend CRUD based template

We building a PRODUCTION GRADE backend .
We'll now extend the Node.js backend we started earlier in PART 1, and add all these advanced features :


🛠 Add these features step-by-step:


1️⃣ Global Error Handler Middleware

src/middlewares/errorMiddleware.js

const errorHandler = (err, req, res, next) => {
  console.error(err.stack);

  const statusCode = res.statusCode ? res.statusCode : 500;

  res.status(statusCode).json({
    message: err.message || 'Internal Server Error',
    stack: process.env.NODE_ENV === 'production' ? null : err.stack,
  });
};

module.exports = { errorHandler };
Enter fullscreen mode Exit fullscreen mode

✅ In src/app.js add at bottom:

const { errorHandler } = require('./middlewares/errorMiddleware');

// After all routes
app.use(errorHandler);
Enter fullscreen mode Exit fullscreen mode

2️⃣ Refresh Token & Access Token System

We'll create:

  • Access Token (short expiry like 15 minutes)
  • Refresh Token (long expiry like 7 days)

✅ Update authController.js:

// Issue Tokens
const issueTokens = (userId) => {
  const accessToken = jwt.sign({ id: userId }, process.env.JWT_SECRET, { expiresIn: '15m' });
  const refreshToken = jwt.sign({ id: userId }, process.env.JWT_REFRESH_SECRET, { expiresIn: '7d' });

  return { accessToken, refreshToken };
};

// Login Controller Updated
exports.login = async (req, res) => {
  const { email, password } = req.body;
  try {
    const user = await User.findOne({ email });
    if (!user) return res.status(400).json({ message: 'Invalid credentials' });

    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) return res.status(400).json({ message: 'Invalid credentials' });

    const tokens = issueTokens(user._id);

    res.status(200).json(tokens);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

// Refresh Token Controller
exports.refreshToken = async (req, res) => {
  const { token } = req.body;
  if (!token) return res.status(401).json({ message: 'No refresh token provided' });

  try {
    const decoded = jwt.verify(token, process.env.JWT_REFRESH_SECRET);
    const tokens = issueTokens(decoded.id);
    res.status(200).json(tokens);
  } catch (err) {
    res.status(403).json({ message: 'Invalid refresh token' });
  }
};
Enter fullscreen mode Exit fullscreen mode

✅ Update src/routes/authRoutes.js:

const { register, login, refreshToken } = require('../controllers/authController');

router.post('/refresh-token', refreshToken);
Enter fullscreen mode Exit fullscreen mode

✅ Update .env:

JWT_REFRESH_SECRET=your_refresh_secret_key
Enter fullscreen mode Exit fullscreen mode

3️⃣ DTOs (Data Transfer Objects)

➔ Create src/dto/user.dto.js

exports.userRegisterDTO = (req) => {
  return {
    name: req.body.name,
    email: req.body.email,
    password: req.body.password
  };
};

exports.userLoginDTO = (req) => {
  return {
    email: req.body.email,
    password: req.body.password
  };
};
Enter fullscreen mode Exit fullscreen mode

✅ Then use in authController.js:

const { userRegisterDTO, userLoginDTO } = require('../dto/user.dto');

// Inside register
const userDTO = userRegisterDTO(req);

// Inside login
const loginDTO = userLoginDTO(req);
Enter fullscreen mode Exit fullscreen mode

4️⃣ Rate Limiting

➔ Install package:

npm install express-rate-limit
Enter fullscreen mode Exit fullscreen mode

➔ Create src/middlewares/rateLimiter.js

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 min
  max: 100, // 100 requests per 15 minutes
  message: "Too many requests from this IP, please try again later",
});

module.exports = { limiter };
Enter fullscreen mode Exit fullscreen mode

✅ Add to app.js:

const { limiter } = require('./middlewares/rateLimiter');

app.use(limiter);
Enter fullscreen mode Exit fullscreen mode

5️⃣ Swagger Documentation

➔ Install:

npm install swagger-jsdoc swagger-ui-express
Enter fullscreen mode Exit fullscreen mode

➔ Create src/swagger/swaggerConfig.js:

const swaggerJsDoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');

const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'Node.js CRUD API',
      version: '1.0.0',
      description: 'Simple CRUD API with JWT Auth'
    },
    servers: [
      {
        url: 'http://localhost:5000'
      }
    ]
  },
  apis: ['./src/routes/*.js'], // Path to your API files
};

const swaggerSpec = swaggerJsDoc(options);

const swaggerDocs = (app) => {
  app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
};

module.exports = swaggerDocs;
Enter fullscreen mode Exit fullscreen mode

✅ In src/app.js:

const swaggerDocs = require('./swagger/swaggerConfig');

swaggerDocs(app);
Enter fullscreen mode Exit fullscreen mode

✅ Example in src/routes/authRoutes.js (add comment above API):

/**
 * @swagger
 * /api/auth/register:
 *   post:
 *     summary: Register new user
 *     tags: [Auth]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               name:
 *                 type: string
 *               email:
 *                 type: string
 *               password:
 *                 type: string
 *     responses:
 *       201:
 *         description: User registered successfully
 *       400:
 *         description: Bad request
 */
router.post('/register', [...], register);
Enter fullscreen mode Exit fullscreen mode

Swagger now at: http://localhost:5000/api-docs


6️⃣ Logging (Morgan + Winston)

➔ Install:

npm install morgan winston
Enter fullscreen mode Exit fullscreen mode

✅ In src/app.js:

const morgan = require('morgan');
const winston = require('winston');

// Morgan HTTP logger
app.use(morgan('dev'));

// Winston custom logger
const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
  ],
});

// Global logger usage example
app.use((req, res, next) => {
  logger.info(`${req.method} ${req.url}`);
  next();
});
Enter fullscreen mode Exit fullscreen mode

7️⃣ Dockerfile

➔ Create Dockerfile in project root:

# Use Node.js LTS base image
FROM node:18

# Set working directory
WORKDIR /app

# Copy package.json and install dependencies
COPY package*.json ./
RUN npm install

# Copy all files
COPY . .

# Expose port
EXPOSE 5000

# Run the app
CMD ["npm", "start"]
Enter fullscreen mode Exit fullscreen mode

✅ Create .dockerignore

node_modules
npm-debug.log
Enter fullscreen mode Exit fullscreen mode

✅ Build & Run Docker:

docker build -t node-crud-app .
docker run -p 5000:5000 node-crud-app
Enter fullscreen mode Exit fullscreen mode

📋 Updated .env example

PORT=5000
MONGO_URI=your_mongo_uri
JWT_SECRET=your_jwt_secret
JWT_REFRESH_SECRET=your_jwt_refresh_secret
JWT_EXPIRES_IN=15m
Enter fullscreen mode Exit fullscreen mode

🏆 Congratulations!

👉 Now your backend:

  • 🔥 Has Global error handling
  • 🔥 Has Access + Refresh tokens
  • 🔥 Has Rate limiting security
  • 🔥 Has DTOs (clean request handling)
  • 🔥 Has API documentation (Swagger)
  • 🔥 Has Logging (Morgan + Winston)
  • 🔥 Is Dockerized for production deployment

🚀 Final API Endpoints

Method URL Description
POST /api/auth/register Register user
POST /api/auth/login Login user
POST /api/auth/refresh-token Get new tokens
GET /api/users (protected) List all users
GET /api-docs API Docs

Top comments (0)