Skip to main content
In this section we demonstrate how to audit software quality at scale using Claude Code. We’ll run an architecture and design analysis against the Express Login Demo, then review the generated findings and precise remediation guidance. This walkthrough is useful for engineers and architects who want to automate codebase-level audits that check architecture, separation of concerns, error handling, security posture, and maintainability.
The repository used for prompts and follow-along artifacts includes the review prompts and generated audit files. This avoids retyping long prompts during the walkthrough and helps reproduce the analysis locally.

Starting Claude Code (CLI)

Here is the initial CLI session used to start Claude Code in the project workspace:
jeremy@MACSTUDIO Express-login-demo % claude

* Welcome to Claude Code!

/help for help, /status for your current setup

cwd: /Users/jeremy/Repos/Claude Code Course/Express-login-demo

> Try "refactor <filepath>"

? for shortcuts

In COMPREHENSIVE_SECURITY_REPORT.md
Rather than retyping long prompts during follow-along, the review prompts used for software and security reviews are included with the course materials.
A dark‑mode browser window showing a GitHub repository page titled "Claude‑Code‑Reviewing‑Prompts" with a list of markdown files (LICENSE, README and various security-related docs). The repo view is open over a code editor window in the background.

Review Prompt (excerpt)

The analysis was driven by a concrete prompt asking Claude to evaluate architecture, produce file-level findings, and recommend precise remediation:
> Initial Software Design Analysis:

Analyze the project architecture and identify:

Evaluate:
1. Is there clear separation of concerns?
2. Which architectural pattern is used (MVC, Layered, Microservices)?
3. Are there any God objects or modules doing too much?
4. Is the dependency flow clean (no circular dependencies)?
5. Rate the modularity (1-10) with justification

Create an architecture diagram showing:
- Layer dependencies
- Data flow
- External service integrations
- Potential bottlenecks

Identify anti-patterns:
- Spaghetti code
- Copy-paste programming
- God classes/modules
- Tight coupling
- Missing abstractions

## Provide:

A structured finding report

A scale of 1/10 on how important each finding is

Remediation: precise code-level fix or config change (snippets welcome), plus defense-in-depth guidance.

## Constraints & style:

Be concrete and cite exact code locations and identifiers.

Prefer minimal, drop-in fix snippets over prose.

Do not invent files or functions that aren't present; if context is missing, mark as Unable to verify and say what code would prove it.

Write this into a markdown file and place it in the audits/ folder.

Claude Code Exploration (excerpt)

Claude explored the repository, read files, searched for patterns and then wrote a detailed report into audits/SOFTWARE_DESIGN_ARCHITECTURE_ANALYSIS.md. The CLI output showed the main actions:
• Update Todos
▢ Explore project structure and identify main components
▢ Analyze architectural patterns and separation of concerns
▢ Identify anti-patterns and code smells
▢ Create architecture diagram and data flow analysis
▢ Generate structured findings report with remediation

• List(~/Repos/Claude Code Course/Express-login-demo)
└ Listed 25 paths (ctrl+r to expand)

• Read(server.js)
└ Read 19 lines (ctrl+r to expand)

Search(pattern: "class|function|async function", output_mode: "content")
└ Found 10827 lines (ctrl+r to expand)

• Write(audits/SOFTWARE_DESIGN_ARCHITECTURE_ANALYSIS.md)

Opened changes in Visual Studio Code ⊕
Save file to continue...
After confirming the write, Claude saved the analysis file. Below are the consolidated excerpts and actionable findings from that report.

Software Design & Architecture Analysis — Key Excerpts

Project: Express Login Demo
Analysis Date: 2025-08-24
Scope: Architecture patterns, design quality, and code organization assessment

Executive summary

  • Overall Architecture Score: 4/10
    The codebase approximates a minimal MVC pattern but contains significant design limitations. Business logic is embedded in route handlers, there is no service or repository abstraction, and error handling is not centralized.
  • Modularity: 3/10 — Flat structure with minimal organization.
  • Maintainability / Testability: Low — Tight coupling and monolithic route handlers hinder unit testing and safe refactoring.
Immediate high-level recommendations:
  • Extract service layer from route handlers (e.g., authentication logic currently in routes/auth.js)
  • Implement centralized error-handling middleware
  • Introduce a data access layer (repositories) and move raw DB queries into it
  • Add input validation and sanitization consistently
  • Add structured logging, monitoring, and rate-limiting

Project structure (observed)

Use this quick reference to understand the current layout detected by the analysis:
PathDescription
server.jsEntry point + middleware setup
routes/auth.jsMonolithic authentication routes containing business logic
config/database.jsDatabase connection
schema.sqlDatabase schema
package.jsonDependencies and scripts
Textual output observed:
express-login-demo/
├── server.js                 # Entry point + middleware setup
├── routes/auth.js            # Authentication routes (monolithic)
├── config/database.js        # Database connection
├── schema.sql                # Database schema
├── package.json              # Dependencies

Architecture assessment

  • Pattern Identified: Minimal MVC-inspired structure
    • Model Layer: Basic PostgreSQL integration via config/database.js
    • Controller Layer: Route handlers in routes/auth.js
    • View Layer: JSON API responses (no frontend)
  • Separation of concerns: 3/10
    • Business logic mixed with route handling
    • Data access performed directly in controllers
    • No dedicated services or repositories
    • Database config is separated (positive)
Data flow summary:
  • Controllers → direct DB queries → return responses
  • No caching or rate-limiting present
  • Potential bottlenecks: single DB pool, synchronous password hashing, and lack of connection limits/retries
Architecture diagram (textual / ASCII):
~~~~~
|                          |
|       Routes Layer       |
|--------------------------|
|    Controllers Layer     |
|--------------------------|
|      Services Layer      |
|--------------------------|
|  Data Access Layer (DAL) |
|--------------------------|
|       Models Layer       |
|                          |
~~~~~
Dependency flow (conceptual):
Controllers  → Services → Repositories → Database
     ↓                ↓
  Models ←────────────┘

Anti-patterns and code smells detected

  • God module: routes/auth.js has too many responsibilities (severity 8/10).
  • Primitive obsession: raw SQL strings and unparameterized queries (risk of SQL injection).
  • Duplication: repeated validation and error responses — candidate for centralized middleware.
  • Tight coupling: direct imports of DB pool and crypto logic into route files.

Concrete Remediation — Code Examples

The report prioritized concrete, minimal-drop-in fixes. Below are the suggested code reorganizations and snippets Claude produced. These are ready to drop into the repository with the listed file paths.
Before applying code changes in production, run the test suite (if any), and perform integration tests against a staging environment. Backup the database and confirm config values for hashing/keys.

1) Simplify routes and delegate to controllers/services

routes/auth.js — thin route definition using express-validator, delegating to a controller:
// routes/auth.js
const express = require('express');
const { body } = require('express-validator');
const authController = require('../controllers/authController');

const router = express.Router();

router.post(
  '/login',
  [ body('email').isEmail().normalizeEmail(), body('password').isLength({ min: 8 }).trim() ],
  authController.login
);

module.exports = router;
Controller delegates to a service:
// controllers/authController.js
const authService = require('../services/authService');

exports.login = async (req, res, next) => {
  try {
    const { email, password } = req.body;
    const { user, token } = await authService.authenticate(email, password);
    res.json({ token, user });
  } catch (err) {
    next(err);
  }
};
Service that encapsulates business logic:
// services/authService.js
class AuthService {
  constructor(userRepository, jwtService, passwordHasher) {
    this.userRepository = userRepository;
    this.jwtService = jwtService;
    this.passwordHasher = passwordHasher;
  }

  async authenticate(email, password) {
    const user = await this.userRepository.findByEmail(email);
    if (!user) {
      const err = new Error('Invalid credentials');
      err.statusCode = 401;
      err.isOperational = true;
      throw err;
    }

    const match = await this.passwordHasher.compare(password, user.password_hash);
    if (!match) {
      const err = new Error('Invalid credentials');
      err.statusCode = 401;
      err.isOperational = true;
      throw err;
    }

    const token = this.jwtService.generateToken({ userId: user.id, email: user.email });
    return { user: this.userRepository.sanitize(user), token };
  }
}

module.exports = new AuthService(require('../repositories/userRepository'), require('../services/jwtService'), require('../utils/passwordHasher'));
Repository using parameterized queries:
// repositories/userRepository.js
const db = require('../config/database');

exports.findByEmail = async (email) => {
  const result = await db.query('SELECT id, email, password_hash FROM users WHERE email = $1', [email]);
  return result.rows[0];
};

exports.sanitize = (user) => {
  if (!user) return null;
  const { password_hash, ...safe } = user;
  return safe;
};

2) Centralized error handler (drop-in)

Create middleware/errorHandler.js:
// middleware/errorHandler.js
const logger = require('../utils/logger');

module.exports = (err, req, res, next) => {
  logger.error(err);
  if (err.isOperational && err.statusCode) {
    return res.status(err.statusCode).json({ error: err.message });
  }
  res.status(500).json({ error: 'Internal server error' });
};
Attach it in server.js after route setup:
const errorHandler = require('./middleware/errorHandler');
// ... after route setup
app.use(errorHandler);

3) Use asynchronous password hashing (avoid blocking the event loop)

// utils/passwordHasher.js
const bcrypt = require('bcrypt');
const SALT_ROUNDS = parseInt(process.env.BCRYPT_SALT_ROUNDS || '12', 10);

exports.hash = async (password) => {
  return bcrypt.hash(password, SALT_ROUNDS);
};

exports.compare = async (password, hash) => {
  return bcrypt.compare(password, hash);
};
Ensure account creation uses the async hash function instead of synchronous APIs.

4) Input validation and sanitizer middleware

Create middleware/sanitizer.js to unify validation error handling:
// middleware/sanitizer.js
const { validationResult } = require('express-validator');

module.exports = (req, res, next) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    const err = new Error('Validation failed');
    err.statusCode = 400;
    err.details = errors.array();
    err.isOperational = true;
    return next(err);
  }
  next();
};
Use this middleware after route validators.

5) Rate limiting for authentication endpoints

Example using express-rate-limit:
const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 10, // limit each IP to 10 requests per windowMs
  message: 'Too many login attempts. Please try again later.'
});

app.use('/auth/login', loginLimiter);

Remediation Summary Table

FindingImportance (1–10)Suggested FixFile(s)
Business logic in routes9Move to services; thin controllersroutes/auth.js → controllers/, services/
No centralized errors8Add middleware errorHandlermiddleware/errorHandler.js
Raw SQL / unparameterized queries9Use repositories and parameterized queriesrepositories/userRepository.js
Synchronous bcrypt usage8Replace with async bcryptutils/passwordHasher.js
Missing validation centralization7Add express-validator + sanitizermiddleware/sanitizer.js
No rate limiting6Add express-rate-limit on auth routesserver.js

Performance & Operational Risks

  • Single DB connection pool without limits — configure pool max connections and backoff/retries.
  • Synchronous password hashing — blocks event loop; switch to async.
  • No caching layer — consider caching session metadata or tokens.
  • No monitoring/metrics — add tracing and metrics to identify hotspots.
  • Current State: Functional but brittle; significant architectural debt.
  • Technical Debt: High — refactoring required to scale safely.
  • Maintainability: Low — monolithic handlers and tight coupling impede change.
  • Testability: Very Low — lack of separation makes unit tests hard to write.
Immediate actions:
  1. Extract business logic into service layer.
  2. Implement centralized error handling.
  3. Create repository/data access layer and remove raw SQL from controllers.
  4. Add validation, sanitization, and rate limiting.
  5. Implement structured logging and monitoring.
Long-term goals:
  • Adopt a layered architecture with dependency inversion / DI.
  • Expand unit and integration test coverage.
  • Add configuration management, observability, and CI checks for architecture quality.

This initial software design analysis was saved to audits/SOFTWARE_DESIGN_ARCHITECTURE_ANALYSIS.md. In later sections we’ll perform deeper design and security audits and use Claude Code to help refactor and iteratively improve the architecture score. References and further reading:

Watch Video