Node.js Project Structure
React does not enforce a folder structure, and neither does Node.js. These are practical patterns that work well for most projects.
A minimal Node.js project
The smallest possible project is a single file:
my-app/
├── index.js
└── package.json
Run npm init -y to create package.json, then create index.js. This is enough to start.
A typical Express project
Once you add Express, routing, and static files, a common structure looks like this:
my-app/
├── src/
│ ├── routes/ # route handlers
│ │ ├── users.js
│ │ └── products.js
│ ├── controllers/ # business logic (optional, for larger apps)
│ ├── middleware/ # custom middleware functions
│ └── index.js # entry point: sets up Express and starts server
├── public/ # static files served by Express
│ ├── index.html
│ └── style.css
├── .env # environment variables (never commit this)
├── .env.example # example env vars with no real values (commit this)
├── .gitignore
├── package.json
└── package-lock.json
package.json scripts
Always include at least a start and dev script:
{
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"test": "jest"
}
}
npm start: runs the app without auto-restart, used in productionnpm run dev: runs nodemon, restarts on file changes during developmentnpm test: runs your test suite
The .gitignore file
Create a .gitignore in your project root. At minimum it should contain:
# Dependencies
node_modules/
# Environment variables (contains secrets)
.env
# Build output
dist/
build/
# OS files
.DS_Store
Thumbs.db
# Logs
*.log
npm-debug.log*
Never commit node_modules/ (it is large and reproducible from package.json) or .env (it contains secrets).
What to commit to git
Commit:
- All source code (
src/,public/) package.jsonandpackage-lock.json.env.example(variable names with no real values).gitignore
Do not commit:
node_modules/.env
routes vs controllers
For small apps, put all logic directly in route handlers. For larger apps, separate the routing (what URLs exist) from the business logic (what to do when a URL is hit).
// routes/users.js: defines the URL structure
router.get('/', getUsers);
router.get('/:id', getUserById);
// controllers/users.js: defines what actually happens
export async function getUsers(req, res) {
const users = await db.query('SELECT * FROM users');
res.json(users);
}
This keeps routes easy to scan at a glance and controllers easy to unit test.
What to read next
- npm : dependencies, scripts, and package-lock.json
- Environment Variables : .env files and dotenv
- Express : building a web server with Express