Express Code Quality
Writing high-quality code is essential for creating maintainable, scalable, and robust Express.js applications. In this guide, we'll explore the key practices and tools that help ensure your Express code remains clean, efficient, and professional.
Why Code Quality Matters
When building Express applications, code quality affects:
- Maintainability: How easily can you or others modify the code later?
- Scalability: How well will the application handle growth?
- Reliability: How dependably will the application run without failures?
- Team Collaboration: How effectively can multiple developers work together?
Essential Code Quality Tools
Linting with ESLint
Linting automatically checks your code for potential errors and enforces consistent style.
Setting up ESLint for Express
- Install ESLint and the Express plugin:
npm install eslint eslint-plugin-node --save-dev
- Create a configuration file (
.eslintrc.js
):
module.exports = {
env: {
node: true,
es6: true
},
extends: [
'eslint:recommended',
'plugin:node/recommended'
],
rules: {
'no-console': 'warn',
'node/exports-style': ['error', 'module.exports'],
'node/no-unsupported-features/es-syntax': [
'error',
{ ignores: ['modules'] }
]
}
};
- Add a lint script to your
package.json
:
{
"scripts": {
"lint": "eslint ."
}
}
Code Formatting with Prettier
Prettier enforces consistent formatting across your codebase.
Setting up Prettier:
- Install Prettier:
npm install prettier --save-dev
- Create a configuration file (
.prettierrc
):
{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"semi": true
}
- Add a format script to your
package.json
:
{
"scripts": {
"format": "prettier --write '**/*.js'"
}
}
Testing Your Express Application
Setting Up Jest for Express
- Install Jest and testing utilities:
npm install jest supertest --save-dev
- Create a simple test for your Express routes:
// tests/routes.test.js
const request = require('supertest');
const app = require('../app');
describe('User API', () => {
test('GET /api/users should return 200 and users array', async () => {
const response = await request(app).get('/api/users');
expect(response.status).toBe(200);
expect(Array.isArray(response.body)).toBe(true);
});
});
- Add test script to
package.json
:
{
"scripts": {
"test": "jest"
}
}
Code Organization Best Practices
Express Project Structure
A well-organized Express application typically follows this structure:
project-root/
├── config/ # Configuration files
├── controllers/ # Request handlers
├── middleware/ # Custom middleware
├── models/ # Data models
├── routes/ # Route definitions
├── services/ # Business logic
├── utils/ # Helper functions
├── tests/ # Test files
├── app.js # Express application setup
└── server.js # Server entry point
Example Implementation
Here's an example of well-structured Express code:
// routes/user.routes.js
const express = require('express');
const userController = require('../controllers/user.controller');
const authMiddleware = require('../middleware/auth.middleware');
const router = express.Router();
router.get('/', userController.getAllUsers);
router.get('/:id', userController.getUserById);
router.post('/', authMiddleware.verifyToken, userController.createUser);
router.put('/:id', authMiddleware.verifyToken, userController.updateUser);
router.delete('/:id', authMiddleware.verifyToken, userController.deleteUser);
module.exports = router;
// controllers/user.controller.js
const UserService = require('../services/user.service');
exports.getAllUsers = async (req, res, next) => {
try {
const users = await UserService.findAll();
res.status(200).json(users);
} catch (error) {
next(error);
}
};
exports.getUserById = async (req, res, next) => {
try {
const user = await UserService.findById(req.params.id);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
res.status(200).json(user);
} catch (error) {
next(error);
}
};
// Additional controller methods...
Error Handling
Good error handling is critical for high-quality Express applications.
Centralized Error Handling
// middleware/error.middleware.js
const errorHandler = (err, req, res, next) => {
const statusCode = err.statusCode || 500;
// Log error for developers
console.error(err.stack);
// Send user-friendly response
res.status(statusCode).json({
status: 'error',
message: err.message || 'Internal Server Error',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
};
module.exports = errorHandler;
// app.js
const express = require('express');
const errorHandler = require('./middleware/error.middleware');
const app = express();
// Routes and other middleware
app.use('/api/users', require('./routes/user.routes'));
// Error handling middleware (must be last!)
app.use(errorHandler);
module.exports = app;
Documentation
Using JSDoc for Code Documentation
/**
* User service for database operations related to users
* @module services/user
*/
/**
* Find user by ID
*
* @async
* @param {string} id - The user's unique identifier
* @returns {Promise<Object|null>} The user object or null if not found
* @throws {Error} If database operation fails
*/
exports.findById = async (id) => {
try {
return await UserModel.findById(id);
} catch (error) {
throw new Error(`Failed to fetch user: ${error.message}`);
}
};
Code Review Checklist
When reviewing Express code, consider these quality aspects:
- Security: Check for potential vulnerabilities
- Performance: Identify bottlenecks or inefficient operations
- Error Handling: Ensure proper error management
- Code Duplication: Look for repeated logic that could be abstracted
- Naming Conventions: Verify clear and consistent naming
- Comments and Documentation: Ensure code is well-explained
Real-world Application: Building a RESTful API
Here's a practical example demonstrating high-quality Express code practices:
// app.js
const express = require('express');
const helmet = require('helmet');
const compression = require('compression');
const morgan = require('morgan');
// Import routes
const productRoutes = require('./routes/product.routes');
const errorHandler = require('./middleware/error.middleware');
// Initialize app
const app = express();
// Security middleware
app.use(helmet());
// Parse JSON requests
app.use(express.json());
// Compress responses
app.use(compression());
// Request logging
if (process.env.NODE_ENV !== 'test') {
app.use(morgan('dev'));
}
// Routes
app.use('/api/products', productRoutes);
// 404 handler
app.use((req, res) => {
res.status(404).json({ message: 'Resource not found' });
});
// Error handler
app.use(errorHandler);
module.exports = app;
Code Quality Metrics
Consider monitoring these key metrics to evaluate your Express code quality:
- Test Coverage: Percentage of code covered by tests
- Complexity: How intricate your functions are (using tools like complexity-report)
- Dependencies: Number and quality of external packages
- Code Duplication: Amount of repeated code
- Issues: Bugs, vulnerabilities, and code smells
Summary
Maintaining high-quality Express code involves a combination of:
- Using proper linting and formatting tools
- Implementing thorough testing
- Organizing code logically
- Handling errors effectively
- Documenting your code
- Conducting regular code reviews
- Monitoring quality metrics
By integrating these practices into your development workflow, you'll create Express applications that are more maintainable, performant, and resilient.
Additional Resources
- Express.js Official Documentation
- JavaScript Testing Best Practices
- Clean Code in JavaScript
- Express API Security Best Practices
Practice Exercises
- Set up ESLint and Prettier in an existing Express project
- Write tests for a set of Express routes using Jest and Supertest
- Refactor an Express application to use the folder structure outlined in this guide
- Implement centralized error handling in an Express application
- Add JSDoc documentation to your Express controllers and services
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)