Best Project Structure in NodeJS [MVC]

Talha Maqsood
3 min readApr 3, 2024

--

Folder Structure NodeJS [MVC]

I am going to make a demo project setup in NodeJS following MVC. So let’s start by creating package.json, Opening the terminal making a new folder, and running “npm init”

mkdir test_project
cd test_project
npm init -y

Now, install the necessary packages.

I’m gonna just setup the project structure so I’m not gonna use any database.

npm i cors dotenv express
npm i nodemon --save-dev

Add a dev and start the script in the package.json

{
"scripts": {
"dev": "nodemon index.js",
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
}
}

So, the final package.json will look like this

{
"name": "test_project",
"version": "1.0.0",
"description": "This is just a test project.",
"main": "index.js",
"scripts": {
"dev": "nodemon index.js",
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Talha Maqsood",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2"
},
"devDependencies": {
"nodemon": "^3.1.0"
}
}

Now, make .env file for environment variables, I'm gonna add port and cors origins in .env

Also, make another file .env.sample and copy all the variables from .env but without value. It's because .env.sample will be pushed to GitHub and it documents the required environment variables and their purpose.

PORT=5000
CORS_OPTIONS=http://localhost:3000,http://localhost:5000

NOTE: CORS_OPTIONS contains the comma separated domains list so, it can be more flexible for large number of domains

Make the index.js file to setup cors options and server

const express = require("express");
const cors = require('cors');
const dotenv = require('dotenv');

const app = express();

// use .env variables
dotenv.config();

// destructure the cors options from .env to make an array of all domains
const CORS_OPTIONS = process.env.CORS_OPTIONS;
let corsOrigins = [];
if (CORS_OPTIONS) {
corsOrigins = CORS_OPTIONS.split(',');
}

// allow cors
app.use(cors({
origin: corsOrigins,
optionsSuccessStatus: 200
}));

// make public folder static to server static files
app.use(express.static('public'));

// parse the request body
app.use(express.json());
app.use(express.urlencoded({extended: true}));

app.get('/', (req, res) => {
res.status(200).send("Server is running!");
});

// make an express server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`App is listening on port ${PORT}`);
});

Now, let's setup the folder structure.

Root
- config
db.js
firebase.js
- public
- src
- controllers
user.controller.js
post.controller.js
- middlewares
auth.middleware.js
- models
user.model.js
post.model.js
- routes
user.route.js
post.route.js
index.route.js
- views
- authentication
login.ejs
signup.ejs
.env
.env.sample
.gitignore
index.js
package.json
package-lock.json

Lastly, add .gitignore file if you want to push your code to GitHub. This file includes all the files and folders which we do not want to push to GitHub

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# github
.git

# intelliJ conf
.idea

This is how I setup my NodeJS project structure depending on the requirements.

GitHub: https://github.com/talhaa99/nodejs-project-setup

Hope this might help! 😊

--

--

Talha Maqsood
Talha Maqsood

Written by Talha Maqsood

Crafting next-level software experiences with Node.js, Laravel, and Blockchain - all while making the complex seem effortless.

No responses yet