DEV Community

Cover image for Organizing a Production-Ready Node.js and Express.js Application
JasGiigli
JasGiigli

Posted on

Organizing a Production-Ready Node.js and Express.js Application

When developing a production-ready Node.js and Express.js application, maintaining a clear and scalable folder structure is vital. Below is a suggested directory layout under a src directory, which organizes your application components for enhanced readability and management.

Image description

my-app/
├── src/
│ ├── config/
│ │ ├── db.js # Database connection configuration
│ │ ├── appConfig.js # Application-specific configuration settings
│ │ └── jwtConfig.js # JWT secret and options for authentication
│ ├── controllers/
│ │ ├── userController.js # Controller for user-related endpoints
│ │ ├── productController.js # Controller for product-related endpoints
│ │ └── authController.js # Controller for authentication-related endpoints
│ ├── middleware/
│ │ ├── authMiddleware.js # Middleware for verifying JWT tokens
│ │ ├── errorHandler.js # Centralized error handling middleware
│ │ └── loggerMiddleware.js # Middleware for logging requests
│ ├── models/
│ │ ├── userModel.js # User data model (schema)
│ │ └── productModel.js # Product data model (schema)
│ ├── routes/
│ │ ├── userRoutes.js # Routes for user-related operations
│ │ ├── productRoutes.js # Routes for product-related operations
│ │ └── authRoutes.js # Routes for authentication
│ ├── services/
│ │ ├── userService.js # Business logic for user-related operations
│ │ ├── productService.js # Business logic for product-related operations
│ │ └── authService.js # Business logic for authentication
│ ├── utils/
│ │ ├── logger.js # Logger utility for logging messages
│ │ ├── dateFormatter.js # Utility for formatting dates
│ │ └── responseFormatter.js # Utility for consistent API responses
│ ├── tests/
│ │ ├── controllers/ # Unit tests for controllers
│ │ │ ├── userController.test.js
│ │ │ └── productController.test.js
│ │ ├── services/ # Unit tests for services
│ │ │ ├── userService.test.js
│ │ │ └── productService.test.js
│ │ └── models/ # Unit tests for models
│ │ ├── userModel.test.js
│ │ └── productModel.test.js
│ ├── .env # Environment variables
│ ├── .gitignore # Files and folders to ignore in git
│ ├── README.md # Project documentation
│ ├── package.json # NPM package manifest
│ └── server.js # Main entry point for the application
├── .env # Environment variables
├── .gitignore # Files and folders to ignore in git
├── README.md # Project documentation
├── package.json # NPM package manifest
└── package-lock.json # Exact versions of NPM dependencies

Detailed Breakdown of Each Folder/File

1. src/config/

db.js: Configuration for connecting to the database (MongoDB, PostgreSQL, etc.).
appConfig.js: General configuration settings for your application, like server port or application name.
jwtConfig.js: Contains the secret key and settings related to JSON Web Tokens for authentication.

2. src/controllers/

userController.js: Contains functions for handling user-related HTTP requests (e.g., registration, fetching user data).
productController.js: Contains functions for handling product-related HTTP requests.
authController.js: Handles authentication processes (login, logout, etc.).

3. src/middleware/

authMiddleware.js: Middleware for authenticating users via JWT. This checks if a request is coming from an authenticated user.
errorHandler.js: Centralized error handling middleware that captures errors and sends a formatted response.
loggerMiddleware.js: Logs incoming requests and other important events for monitoring.

4. src/models/

userModel.js: Defines the schema and model for user data, typically using Mongoose for MongoDB.
productModel.js: Defines the schema and model for product data.

5. src/routes/

userRoutes.js: Contains routes related to user operations (e.g., registration, profile management).
productRoutes.js: Contains routes related to product operations.
authRoutes.js: Contains routes specifically for authentication.

6. src/services/

userService.js: Contains business logic related to user operations, separating it from controllers.
productService.js: Contains business logic related to product operations.
authService.js: Handles authentication logic, including token generation and validation.

7. src/utils/

logger.js: Utility for logging messages and errors consistently across the application.
dateFormatter.js: A utility function for formatting date objects.
responseFormatter.js: Standardizes API responses for consistency.

8. src/tests/

controllers/: Contains unit tests for each controller to ensure they handle requests correctly.
services/: Contains unit tests for service functions to verify business logic.
models/: Contains tests for model validations and functionalities.

9. Root Files

.env: Store sensitive information such as API keys, database credentials, and other environment variables.
.gitignore: Specify files and directories that should not be tracked by Git (e.g., node_modules, .env).
README.md: Documentation about the project, how to set it up, usage instructions, and any other relevant information.
package.json: Lists project dependencies, scripts, and metadata.
package-lock.json: Locks dependency versions to ensure consistent installs.
server.js: The entry point of the application, where you initialize the Express server and middleware.

Example Implementation of Key Files

src/config/db.js

// src/config/db.js
`const mongoose = require('mongoose');
const config = require('./appConfig');

const connectDB = async () => {
try {
await mongoose.connect(config.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB connected successfully');
} catch (error) {
console.error('MongoDB connection failed:', error.message);
process.exit(1);
}
};
module.exports = connectDB;`

src/middleware/authMiddleware.js

// src/middleware/authMiddleware.js
`const jwt = require('jsonwebtoken');
const config = require('../config/jwtConfig');

const authMiddleware = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) {
return res.status(403).json({ message: 'Access denied. No token provided.' });
}

try {
const decoded = jwt.verify(token, config.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ message: 'Invalid token.' });
}
};

module.exports = authMiddleware;`

src/server.js

// src/server.js
` const express = require('express');

const mongoose = require('mongoose');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const userRoutes = require('./routes/userRoutes');
const productRoutes = require('./routes/productRoutes');
const authRoutes = require('./routes/authRoutes');
const errorHandler = require('./middleware/errorHandler');
dotenv.config(); // Load environment variables from .env file
const app = express();
const PORT = process.env.PORT || 3000;
// Connect to the database
connectDB();
// Middleware
app.use(express.json()); // Parse incoming JSON requests
// Routes
app.use('/api/users', userRoutes);
app.use('/api/products', productRoutes);
app.use('/api/auth', authRoutes);
// Error Handling Middleware
app.use(errorHandler);
// Start the server
app.listen(PORT, () => {
console.log(Server is running on http://localhost:${PORT});
});`

Conclusion

By adopting this structured approach to organizing your Node.js and Express.js application under the src directory, you can create a production-ready project that is easy to maintain and scale. This organization separates concerns and improves clarity, enabling better collaboration and development practices as your application grows. Be sure to implement unit tests, error handling, logging, and proper environment management for a robust application.

Top comments (0)