Episode 3 — NodeJS MongoDB Backend Architecture / 3.13 — Production Project Structure

3.13.b --- File Naming and Git Configuration

In one sentence: Consistent file naming and disciplined Git configuration are the invisible backbone of team productivity --- they eliminate bikeshedding, prevent accidental secret leaks, and make every commit message a searchable log entry.

Navigation: <- 3.13.a Application Structure | 3.13.c --- Configuration Management ->


1. File naming conventions

File naming may seem trivial, but inconsistency creates real friction: developers waste time guessing whether the user model is User.js, user.js, user.model.js, or userModel.js.

The three common styles

ConventionExampleWhen to use
kebab-caseuser-profile.jsMost common in Node.js projects; file systems treat it consistently
camelCaseuserProfile.jsMatches JavaScript variable naming; popular in some teams
PascalCaseUserProfile.jsClasses and React components; signals "this exports a class"

The case-sensitivity trap

macOS and Windows file systems are case-insensitive by default. This means User.js and user.js are the same file locally but different files on Linux servers (where your code deploys). This causes mysterious "module not found" errors in production.

Rule: Pick one convention and enforce it. kebab-case is the safest choice for Node.js because it avoids case-sensitivity issues entirely.


2. Naming patterns by file type

A suffix-based naming pattern makes every file's role instantly clear:

File typePatternExample
Model<resource>.model.jsuser.model.js
Controller<resource>.controller.jsuser.controller.js
Service<resource>.service.jsuser.service.js
Route<resource>.routes.jsuser.routes.js
Validator<resource>.validator.jsuser.validator.js
Middleware<name>.middleware.jsauth.middleware.js
Test<name>.test.js or <name>.spec.jsuser.service.test.js
Config<name>.config.jsecosystem.config.js

Alternative: PascalCase for models only

Some teams use PascalCase for model files because Mongoose models export a PascalCase constructor:

// User.js (PascalCase file) --- exports `User` model
const User = require('../models/User');

// vs

// user.model.js (kebab-style with suffix) --- same export
const User = require('../models/user.model');

Both work. The key is consistency within a project. The suffix approach (.model.js) is more descriptive when you have many files open in your editor.


3. Git configuration: .gitignore essentials

The .gitignore file is your first line of defense against committing secrets, build artifacts, and bloat.

Complete .gitignore for Node.js projects

# Dependencies
node_modules/
package-lock.json  # optional: some teams commit this, some don't

# Environment variables (NEVER commit secrets)
.env
.env.local
.env.*.local

# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Build output
dist/
build/
.next/

# Test coverage
coverage/
.nyc_output/

# OS files
.DS_Store
Thumbs.db
*.swp
*.swo

# IDE files
.vscode/
.idea/
*.sublime-project
*.sublime-workspace

# Uploaded files (keep the folder, ignore contents)
public/uploads/*
!public/uploads/.gitkeep

# Temporary files
tmp/
temp/
*.tmp

The .gitkeep trick

Git does not track empty directories. If you need an uploads/ folder to exist in the repo:

mkdir -p public/uploads
touch public/uploads/.gitkeep

Then in .gitignore:

public/uploads/*
!public/uploads/.gitkeep

This keeps the directory structure but ignores uploaded files.


4. .gitignore templates

GitHub maintains official templates at github.com/github/gitignore. The Node.js template covers the essentials. You can generate one when creating a new repository or use:

# Download the Node.js template
curl -o .gitignore https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore

Always review and customise the template for your project's specific needs.


5. Git commit message conventions (Conventional Commits)

Conventional Commits is a specification that adds structure to commit messages, enabling automated changelogs and semantic versioning.

Format

<type>(<scope>): <description>

[optional body]

[optional footer]

Types

TypeWhen to useExample
featNew featurefeat(auth): add password reset endpoint
fixBug fixfix(user): correct email validation regex
docsDocumentation onlydocs(readme): add API setup instructions
styleFormatting, no logic changestyle(lint): fix eslint warnings
refactorCode change, no feature/fixrefactor(post): extract slug generation to utility
testAdding or updating teststest(auth): add login integration tests
choreBuild, config, toolingchore(deps): update mongoose to v8
perfPerformance improvementperf(query): add index to posts.slug field
ciCI/CD changesci(github): add Node.js 20 to test matrix
revertRevert a previous commitrevert: revert feat(auth) commit abc123

Good vs bad commit messages

BadGood
fix stufffix(auth): return 401 instead of 500 for expired tokens
updatefeat(post): add pagination to GET /posts endpoint
WIPwip(comment): partial implementation of nested replies
changesrefactor(middleware): extract rate limiter to separate file

Breaking changes

feat(api)!: change response format from array to paginated object

BREAKING CHANGE: GET /api/v1/posts now returns { data: [], meta: { page, limit, total } }
instead of a plain array. All clients must update their response parsing.

The ! after the scope and the BREAKING CHANGE footer both signal a major version bump.


6. Branch naming strategies

PrefixPurposeExample
feature/New functionalityfeature/user-avatar-upload
bugfix/Non-urgent bug fixbugfix/email-validation-crash
hotfix/Urgent production fixhotfix/jwt-token-expiry
release/Release preparationrelease/v2.1.0
docs/Documentation updatesdocs/api-endpoints
refactor/Code restructuringrefactor/extract-services
test/Test additionstest/auth-integration

Branch naming rules

feature/short-descriptive-name     # Good
feature/JIRA-123-add-user-search   # Good (with ticket number)
Feature/MyNewThing                 # Bad (inconsistent casing)
new-feature                        # Bad (no prefix)
feature/this-is-a-very-long-branch-name-that-nobody-wants-to-type  # Bad

Git flow (simplified)

main (production-ready)
  └── develop (integration branch)
       ├── feature/user-auth
       ├── feature/post-crud
       └── bugfix/login-error

7. .editorconfig for consistent formatting

Different editors have different default settings. .editorconfig ensures everyone on the team uses the same indentation, line endings, and charset --- regardless of their IDE.

# .editorconfig
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab

What each setting does

SettingValueWhy
indent_stylespaceConsistent rendering across editors and GitHub
indent_size2Node.js community standard
end_of_linelfPrevents Windows \r\n from polluting diffs
charsetutf-8Universal encoding
trim_trailing_whitespacetrueEliminates noise in diffs
insert_final_newlinetruePOSIX standard; prevents "no newline at end of file" warnings

Most editors (VS Code, WebStorm, Vim) support .editorconfig natively or with a plugin.


8. Husky for Git hooks

Husky makes it easy to run scripts before Git operations --- ensuring code quality before code reaches the repository.

Installation and setup

# Install Husky
npm install -D husky

# Initialize Husky (creates .husky/ directory)
npx husky init

This creates a .husky/ directory and adds a prepare script to package.json:

{
  "scripts": {
    "prepare": "husky"
  }
}

Pre-commit hook

The pre-commit hook runs before every commit. If it exits with a non-zero code, the commit is blocked.

# .husky/pre-commit
npm run lint
npm run format:check

Pre-push hook

The pre-push hook runs before every push. Use it for slower checks like tests.

# .husky/pre-push
npm test

Common hook scripts

HookWhen it runsTypical use
pre-commitBefore git commitLint, format check, type check
commit-msgAfter writing commit messageValidate Conventional Commits format
pre-pushBefore git pushRun test suite
post-mergeAfter git merge / git pullAuto-run npm install if package.json changed

Commit message validation with commitlint

npm install -D @commitlint/cli @commitlint/config-conventional
// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
};
# .husky/commit-msg
npx --no -- commitlint --edit ${1}

Now any commit message that does not follow Conventional Commits format is rejected.


9. Putting it all together: project root files

project-root/
├── .editorconfig          --- Editor consistency
├── .eslintrc.js           --- Linting rules (see 3.13.e)
├── .gitignore             --- Files Git should ignore
├── .husky/                --- Git hooks
│   ├── pre-commit
│   ├── pre-push
│   └── commit-msg
├── .prettierrc            --- Formatting rules (see 3.13.e)
├── .env                   --- Local environment (NEVER committed)
├── .env.example           --- Template for team (committed)
├── commitlint.config.js   --- Commit message rules
├── package.json           --- Project metadata and scripts
├── server.js              --- Entry point
└── src/                   --- Application code

10. Key takeaways

  1. Pick one naming convention (prefer kebab-case with .model.js / .controller.js suffixes) and enforce it.
  2. .gitignore is not optional --- commit it first, before any code. Always exclude node_modules/, .env, logs, and OS files.
  3. Conventional Commits (feat:, fix:, chore:) make your Git history searchable and enable automated tooling.
  4. Branch prefixes (feature/, bugfix/, hotfix/) communicate intent at a glance.
  5. .editorconfig eliminates formatting wars between developers using different editors.
  6. Husky + commitlint automate quality gates so bad code and bad commit messages never reach the repository.

Explain-It Challenge

Explain without notes:

  1. Why is kebab-case safer than PascalCase for file names in Node.js projects deployed on Linux?
  2. Write a .gitignore entry that keeps the public/uploads/ directory but ignores all files inside it.
  3. A commit message says "fixed login". Rewrite it following Conventional Commits format and explain each part.

Navigation: <- 3.13.a Application Structure | 3.13.c --- Configuration Management ->