Git Workflow and Version Control for Full-Stack Projects

โ–ถ Try It Yourself

Version control is not optional in professional development โ€” it is as fundamental as saving your work. Git tracks every change to your codebase, lets you experiment safely on branches, collaborate with teammates without overwriting each other’s work, and roll back to any previous state. For a MEAN Stack project where frontend and backend are developed in parallel, a clear Git workflow prevents the two tracks from colliding and ensures the main branch always contains working code. In this lesson you will set up Git for the monorepo, understand branching strategy, write meaningful commit messages, configure a proper .gitignore, and establish the workflow that will serve you throughout the rest of this course and in real team environments.

Git Branching Strategies

Strategy Branches Best For
Git Flow main, develop, feature/*, release/*, hotfix/* Software with scheduled releases
GitHub Flow main + feature branches โ†’ PR โ†’ merge Continuous deployment โ€” most web apps
Trunk-Based Development main only + short-lived branches CI/CD teams with feature flags

Branch Naming Conventions

Type Pattern Example
Feature feature/description feature/task-crud-api
Bug fix fix/description fix/login-token-expiry
Chore chore/description chore/update-dependencies
Documentation docs/description docs/api-readme
Release release/version release/v1.2.0

Conventional Commits โ€” Commit Message Format

Type When to Use Example
feat New feature feat(tasks): add CRUD API endpoints
fix Bug fix fix(auth): correct JWT expiry handling
chore Maintenance, dependencies chore: update mongoose to v8
docs Documentation only docs: add API endpoint documentation
refactor Code change without behaviour change refactor(tasks): extract service layer
test Adding or updating tests test(auth): add login integration test
style Formatting only โ€” no logic change style: apply prettier formatting
perf Performance improvement perf(tasks): add MongoDB index on userId
Note: Conventional Commits is a specification for writing commit messages in a structured format: type(scope): description. This format enables automated tooling โ€” CHANGELOG generation, semantic versioning, and release automation. Even if you work alone, adopting this format now builds a habit that every professional team expects.
Tip: Commit frequently โ€” every time you reach a stable point where the code works. Small commits are easier to review, easier to revert, and easier to understand. A commit message like feat(auth): add bcrypt password hashing tells you exactly what changed and why, even six months later. “various fixes” tells you nothing.
Warning: Never commit directly to the main branch, even on a solo project. Always create a feature branch, make your changes, then merge back to main. This discipline means main always contains known-working code, and you have a clean history that shows when each feature was added. It also prepares you for team workflows where direct commits to main are blocked.

Setup โ€” Git for the MEAN Stack Project

# โ”€โ”€ Initialize Git for the monorepo โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
cd task-manager
git init
git branch -M main     # rename default branch to main

# โ”€โ”€ Configure Git (if not done globally) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
git config --global user.name  "Your Name"
git config --global user.email "your@email.com"
git config --global init.defaultBranch main
git config --global core.autocrlf input  # macOS/Linux โ€” normalise line endings

# โ”€โ”€ Create the .gitignore โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
cat > .gitignore << 'EOF'
# Dependencies
node_modules/
.npm/

# Environment variables โ€” NEVER commit these
.env
.env.local
.env.*.local

# Angular build output
frontend/dist/
frontend/.angular/

# IDE and OS files
.vscode/settings.json
.idea/
.DS_Store
Thumbs.db

# Logs
*.log
npm-debug.log*

# Test coverage
coverage/

# MongoDB local data
data/db/
EOF

# โ”€โ”€ First commit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
git add .
git commit -m "chore: initial MEAN Stack project scaffold

- Add monorepo structure with frontend/ and backend/
- Configure .gitignore for Node, Angular, and environment files
- Add root package.json with concurrently dev scripts"

# โ”€โ”€ Connect to GitHub โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Create a new repo on GitHub first, then:
git remote add origin https://github.com/yourusername/task-manager.git
git push -u origin main

Daily Development Workflow โ€” GitHub Flow

# โ”€โ”€ Start a new feature โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
git checkout main
git pull origin main                        # always pull latest first

git checkout -b feature/task-crud-api       # create and switch to feature branch

# โ”€โ”€ Develop the feature โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ... write code ...
git status                                  # see what changed
git diff                                    # see exact changes
git add backend/src/routes/task.routes.js
git add backend/src/controllers/task.controller.js
git commit -m "feat(tasks): add GET and POST API endpoints"

# ... write more code ...
git add .
git commit -m "feat(tasks): add PUT and DELETE endpoints with auth protection"

# โ”€โ”€ Push and open a Pull Request โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
git push origin feature/task-crud-api

# Go to GitHub โ†’ New Pull Request โ†’ Merge into main

# โ”€โ”€ After PR is merged โ€” clean up โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
git checkout main
git pull origin main
git branch -d feature/task-crud-api         # delete local branch
git push origin --delete feature/task-crud-api  # delete remote branch

# โ”€โ”€ Useful Git commands โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
git log --oneline --graph --decorate        # visual branch history
git stash                                   # temporarily save uncommitted changes
git stash pop                               # restore stashed changes
git diff HEAD~1                             # compare with previous commit
git show abc1234                            # show a specific commit
git restore filename.js                     # discard changes to a file
git reset HEAD~1 --soft                     # undo last commit, keep changes staged

Example Commit History โ€” Well-Structured Project

* a1b2c3d (HEAD -> main) feat(frontend): add task list component with async pipe
* d4e5f6a feat(frontend): add task service with HttpClient
* b7c8d9e feat(tasks): add Mongoose Task model and schema
* e1f2g3h feat(tasks): add CRUD REST endpoints with validation
* h4i5j6k feat(auth): add JWT authentication middleware
* k7l8m9n feat(auth): add login and register endpoints
* n1o2p3q feat(auth): add bcrypt password hashing in User model
* q4r5s6t feat(auth): add User Mongoose model and schema
* t7u8v9w chore: configure MongoDB connection and reconnect logic
* w1x2y3z chore: add express app setup with cors, helmet, body parser
* z4a5b6c chore: initial MEAN Stack project scaffold

How It Works

Step 1 โ€” Git Tracks the Entire Project as One History

A single Git repository at the root of your monorepo tracks changes across both frontend/ and backend/. Git records every change as a commit โ€” a snapshot of all modified files at a specific point in time. You can jump back to any commit: git checkout abc1234 restores the entire project (both frontend and backend) to exactly that state.

Step 2 โ€” Branches Are Cheap โ€” Use Them Freely

A Git branch is just a pointer to a commit โ€” creating one takes milliseconds and uses negligible disk space. Branch for every feature, no matter how small. This means you can always switch back to main and start a different task without losing your work. Use git stash when you need to switch contexts before a feature is commit-ready.

Step 3 โ€” .gitignore Prevents Disasters

The .gitignore file tells Git which files and folders to never track. node_modules/ can contain hundreds of thousands of files โ€” committing them would make the repo enormous and slow. .env contains secrets โ€” committing it exposes credentials publicly if the repo is ever made public. The Angular dist/ folder is generated from source โ€” committing build artifacts creates unnecessary merge conflicts.

Step 4 โ€” Conventional Commits Enable Automated Tooling

The feat, fix, chore prefixes are not just cosmetic. Tools like standard-version and release-please read your commit history and automatically determine the next semantic version number (major/minor/patch) and generate a CHANGELOG. A commit starting with feat: triggers a minor version bump. A fix: triggers a patch. A commit with BREAKING CHANGE: in the body triggers a major version bump.

Step 5 โ€” Pull Before You Push โ€” Always

git pull origin main before creating a new branch ensures your branch starts from the latest code. Pulling before pushing prevents rejected push errors when teammates have pushed in the meantime. In team workflows, always rebase or merge the latest main into your feature branch before opening a Pull Request โ€” this makes the PR diff clean and reduces the chance of merge conflicts during review.

Real-World Example: Pre-commit Hooks with Husky

# Install Husky โ€” runs scripts before every commit
cd task-manager
npm install -D husky lint-staged

# Initialize Husky
npx husky init
echo "npx lint-staged" > .husky/pre-commit

# Configure lint-staged in root package.json
# Add:
# "lint-staged": {
#   "backend/src/**/*.js":    ["eslint --fix", "git add"],
#   "frontend/src/**/*.ts":   ["eslint --fix", "prettier --write", "git add"],
#   "frontend/src/**/*.html": ["prettier --write", "git add"]
# }

# Now every git commit automatically:
# 1. Runs ESLint and fixes auto-fixable issues
# 2. Runs Prettier to format the code
# 3. Adds the formatted files back to the commit
# If ESLint finds unfixable errors โ€” the commit is ABORTED

Common Mistakes

Mistake 1 โ€” Giant commits with vague messages

โŒ Wrong โ€” impossible to understand or revert selectively:

git add .
git commit -m "stuff"       # what stuff?
git commit -m "wip"         # work in progress โ€” but what?
git commit -m "fix bug"     # which bug? where? how?

โœ… Correct โ€” small, focused commits with descriptive messages:

git commit -m "fix(auth): handle expired JWT with 401 response instead of 500"

Mistake 2 โ€” Committing node_modules

โŒ Wrong โ€” node_modules committed: repo is gigabytes, Git is unusable:

git add .    # if .gitignore is missing or wrong โ€” adds node_modules!
# 200,000+ files added โ€” git log, git status are now extremely slow

โœ… Correct โ€” verify .gitignore before first commit:

git status   # check what will be committed before git add
# If node_modules appears โ€” fix .gitignore first
echo "node_modules/" >> .gitignore

Mistake 3 โ€” Never pulling โ€” diverged branches

โŒ Wrong โ€” working on stale code for days:

git checkout -b feature/my-feature   # branched from week-old main
# ... 3 days of work ...
git push   # rejected โ€” main has moved ahead 20 commits
git merge main   # massive conflict โ€” painful resolution

โœ… Correct โ€” regularly sync with main:

git fetch origin
git rebase origin/main   # keep feature branch up to date daily

▶ Try It Yourself

Quick Reference โ€” Git Commands for MEAN Stack Development

Task Command
Start new feature git checkout main && git pull && git checkout -b feature/name
Stage all changes git add .
Commit with message git commit -m "feat(scope): description"
Push branch git push origin feature/name
See history git log --oneline --graph
Discard file changes git restore filename
Stash work in progress git stash / git stash pop
Undo last commit (keep changes) git reset HEAD~1 --soft
Delete merged branch git branch -d feature/name

🧠 Test Yourself

You are starting work on a new login feature. What is the correct first step before writing any code?





โ–ถ Try It Yourself