Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

6. How to Setup a Project Professionally

Lesson 6 of 23 in the free Backend Development notes on Siksha Sarovar, written by Rohit Jangra.

Why Professional Setup Matters

The way you structure a Node.js project from day one directly affects how easy it is to maintain, scale, and collaborate on as the project grows. A professional folder structure, proper tooling configuration, and clean separation of concerns are what distinguish a junior developer's side project from a production-grade backend.

---

Professional Folder Structure

my-backend/
├── src/
│   ├── controllers/      # Request handlers (business logic)
│   ├── routes/           # Express Router definitions
│   ├── models/           # Mongoose schemas and models
│   ├── middlewares/      # Custom Express middlewares
│   ├── utils/            # Reusable utility functions
│   ├── config/           # Configuration files (db, cloudinary, etc.)
│   ├── constants/        # App-wide constants and enums
│   └── types/            # TypeScript type declarations (if using TS)
├── public/               # Temp file storage (multer uploads)
├── .env                  # Environment variables (not committed)
├── .env.example          # Template for env vars (committed)
├── .gitignore
├── .eslintrc.json
├── .prettierrc
├── nodemon.json
├── package.json
├── tsconfig.json         # (if using TypeScript)
├── index.js              # Entry point — starts the server
└── src/app.js            # Express app configuration (no server.listen here)

---

Why Separate app.js from index.js

This is a common industry pattern with clear benefits:

src/app.js — Express application setup:

  • Creates express() instance
  • Registers all global middleware (cors, json parser, cookie-parser, morgan)
  • Mounts all routers (app.use('/api/users', userRouter))
  • Does NOT call app.listen()
  • Exported as export default app
  • Easy to import in tests without starting the server

index.js (root level) — Server entry point:

  • Imports the app from src/app.js
  • Connects to the database
  • Only after successful DB connection, starts the server with app.listen()
  • Handles top-level async/await errors and process exit

This separation means tests can import the app without actually starting the server and binding to a port.

---

package.json Scripts

{
  "scripts": {
    "dev": "nodemon src/index.js",
    "start": "node src/index.js",
    "build": "tsc",
    "start:prod": "node dist/index.js",
    "lint": "eslint src --ext .js,.ts",
    "lint:fix": "eslint src --ext .js,.ts --fix",
    "format": "prettier --write src/**/*.{js,ts}"
  }
}

---

nodemon Configuration

nodemon.json controls nodemon's behaviour:

{
  "watch": ["src"],
  "ext": "js,ts,json",
  "ignore": ["src/**/*.test.js", "node_modules"],
  "exec": "node src/index.js"
}

With TypeScript: use ts-node instead: "exec": "ts-node src/index.ts"

---

ESLint + Prettier Setup

ESLint catches code errors and enforces consistent code style. Prettier formats code automatically.

npm install -D eslint prettier eslint-config-prettier eslint-plugin-prettier
npx eslint --init

``.eslintrc.json`` example:

{
  "env": { "node": true, "es2021": true },
  "extends": ["eslint:recommended", "plugin:prettier/recommended"],
  "rules": {
    "no-console": "warn",
    "no-unused-vars": "error"
  }
}

``.prettierrc`` example:

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5"
}

---

dotenv Setup

// src/config/config.js — centralised config object
import dotenv from 'dotenv';
dotenv.config();

export const config = {
  port: process.env.PORT || 5000,
  mongoUri: process.env.MONGODB_URI,
  jwtSecret: process.env.JWT_SECRET,
  jwtRefreshSecret: process.env.JWT_REFRESH_SECRET,
  jwtExpiry: process.env.JWT_EXPIRY || '15m',
  jwtRefreshExpiry: process.env.JWT_REFRESH_EXPIRY || '7d',
  nodeEnv: process.env.NODE_ENV || 'development',
  corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:5173',
  cloudinary: {
    cloudName: process.env.CLOUDINARY_CLOUD_NAME,
    apiKey: process.env.CLOUDINARY_API_KEY,
    apiSecret: process.env.CLOUDINARY_API_SECRET,
  },
};

Always access config through this object, not directly through process.env. This makes it easy to validate, mock in tests, and see all config in one place.

---

.gitignore for Node.js

node_modules/
.env
.env.local
dist/
build/
*.log
public/temp/*
.DS_Store
coverage/

---

cross-env for Windows/Mac Compatibility

Some npm scripts use environment variables inline: NODE_ENV=production node index.js. This syntax does not work on Windows Command Prompt. Use cross-env:

npm install -D cross-env
"scripts": {
  "start:prod": "cross-env NODE_ENV=production node src/index.js"
}

---

Module Exports Pattern

Use named exports for models, utilities, controllers, and middleware. Use default export only for the Express app and database connection function. This makes imports clear and explicit:

// ❌ Avoid: default export for everything
export default router;

// ✅ Prefer: named exports
export { userRouter, authRouter };
export { UserModel };
export { asyncHandler, ApiError, ApiResponse };
Create src/index.js barrel files inside each directory to re-export everything: export { UserModel } from './User.model.js'. This allows clean imports elsewhere: import { UserModel } from '../models'.