Module 1: Installation, Setup, and Initial Documentation
Overview
In this module, we will set up the development environment for our Flight Reservation System, focusing on backend infrastructure. We’ll install necessary applications, servers, and dependencies, and connect our project to GitHub using Visual Studio Code. Additionally, we’ll document our setup process and create an application schema/diagram to outline the architecture of our system.
Step-by-Step Guide
1. Installation of Applications and Dependencies
1.1 Install Node.js and npm
Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine. npm is the package manager for Node.js.
- Download and install Node.js from the official website.
- Verify the installation
node -v
npm -v
1.2 Install Visual Studio Code
Visual Studio Code (VS Code) is a powerful, open-source code editor.
- Download and install VS Code from the official website.
1.3 Install Git
Git is a version control system that lets you manage and keep track of your source code history.
- Download and install Git from the official website.
- Verify the installation
git --version
1.4 Install Postman
Postman is a collaboration platform for API development.
- Download and install Postman from the official website.
2. Setting Up the Project
2.1 Initialize a New Node.js Project
Create a new directory for the project:
mkdir flight-reservation-system
cd flight-reservation-system
Initialize a new Node.js project:
npm init -y
2.2 Install Essential Dependencies
We will need several npm packages for our backend development:
- Express: Fast, unopinionated, minimalist web framework for Node.js.
- Mongoose: Elegant MongoDB object modeling for Node.js.
- dotenv: Loads environment variables from a
.env
file. - bcrypt: Library to hash passwords.
- jsonwebtoken: Library to work with JSON Web Tokens.
- cors: Middleware to enable CORS (Cross-Origin Resource Sharing).
Install these packages:
npm install express mongoose dotenv bcrypt jsonwebtoken cors
Here’s a brief overview of each package:
- express: A minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
- mongoose: An Object Data Modeling (ODM) library for MongoDB and Node.js. It manages relationships between data, provides schema validation, and is used to translate between objects in code and the representation of those objects in MongoDB.
- dotenv: A module that loads environment variables from a
.env
file intoprocess.env
. It’s used to store configuration variables in a centralized location. - bcrypt: A library to help you hash passwords. It’s designed to be computationally intensive to slow down brute-force attacks.
- jsonwebtoken: A library to sign, verify, and decode JSON Web Tokens (JWT). It’s commonly used for authentication and authorization.
- cors: A package for providing a Connect/Express middleware that can be used to enable Cross-Origin Resource Sharing (CORS) with various options.
2.3 Setup Project Structure
Create a basic directory structure for our project:
mkdir config controllers models routes middleware utils
touch server.js
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
2.4 Setup Git and GitHub
Initialize a git repository:
git init
Create a .gitignore
file to exclude node_modules
and other unnecessary files:
touch .gitignore
echo "node_modules/" >> .gitignore
Create a repository on GitHub and follow the instructions to link it to your local project:
git remote add origin <repository_url>
git add .
git commit -m "Initial commit"
git push -u origin master
2.5 Connect VS Code to GitHub
- Open your project in VS Code.
- Use the Source Control panel to commit changes and push them to GitHub.
3. Application Schema/Diagram
We will create a high-level diagram to outline the architecture of our flight reservation system. This includes entities like flights, passengers, airlines, airports, bookings, and payments, and their relationships.
4. Documentation
Documenting our setup process is crucial for future reference and onboarding new developers.
Create a README.md
file in the root directory of your project:
touch README.md
Add the following content to README.md
:
# Flight Reservation System
## Overview
This project is a backend system for managing airline reservations. It includes features such as flight management, passenger management, airline management, airport management, booking management, and payment processing.
## Installation
1. Install Node.js and npm from [Node.js](https://nodejs.org/).
2. Install Git from [Git](https://git-scm.com/).
3. Clone the repository:
```sh
git clone <repository_url>
Install dependencies:
npm install
Project Structure
config/
: Configuration files.controllers/
: Controller functions for handling requests.models/
: Mongoose models for MongoDB.routes/
: API routes.middleware/
: Middleware functions.utils/
: Utility functions.server.js
: Entry point for the application.
Running the Project
- Start the development server
npm start
Contributing
- Fork the repository.
- Create a new branch (
git checkout -b feature-branch
). - Make your changes.
- Commit your changes (
git commit -am 'Add new feature'
). - Push to the branch (
git push origin feature-branch
). - Create a new Pull Request.
In this module, we have successfully set up the development environment for our Flight Reservation System. We installed necessary applications and dependencies, configured our project structure, connected to GitHub, and documented the setup process. The next module will focus on designing the database schema, which will be crucial for managing our data efficiently.
Module 2: Database Design
Overview
In this module, we will design the database schema for our Flight Reservation System. This design will cover all necessary entities, their attributes, and the relationships between them. We will use MongoDB as our database, and Mongoose as the ORM (Object-Relational Mapping) tool to interact with the database.
Entity Descriptions and Relationships
Based on the provided features, we will create the following entities:
- Flight
- Passenger
- Airline
- Airport
- Booking
- Payment
Entity Definitions
Flight
- FlightID (Primary Key)
- FlightNumber
- DepartureDateTime
- ArrivalDateTime
- OriginAirportCode (Foreign Key)
- DestinationAirportCode (Foreign Key)
- AvailableSeats
- AirlineID (Foreign Key)
Passenger
- PassengerID (Primary Key)
- FirstName
- LastName
- PassportNumber
Airline
- AirlineID (Primary Key)
- AirlineName
- ContactNumber
- OperatingRegion
Airport
- AirportCode (Primary Key)
- AirportName
- Location
- Facilities
Booking
- BookingID (Primary Key)
- FlightID (Foreign Key)
- PassengerID (Foreign Key)
- PaymentStatus
Payment
- PaymentID (Primary Key)
- BookingID (Foreign Key)
- PaymentMethod
- Amount
- TransactionDateTime
Relationships
Flight – Booking Relationship
- One-to-Many: One flight can have multiple bookings, but each booking corresponds to only one flight.
Passenger – Booking Relationship
- One-to-Many: One passenger can make multiple bookings, but each booking corresponds to only one passenger.
Flight – Airport Relationship
- Many-to-One: Multiple flights can depart from or arrive at the same airport, but each flight has only one origin and one destination airport.
Airline – Flight Relationship
- One-to-Many: One airline can operate multiple flights, but each flight is operated by only one airline.
Payment – Booking Relationship
- One-to-One: Each payment corresponds to only one booking, and each booking can have only one payment.
Database Schema
We will now create Mongoose models to represent these entities and their relationships.
Flight Model
const mongoose = require('mongoose');
const FlightSchema = new mongoose.Schema({
FlightNumber: { type: String, required: true, unique: true },
DepartureDateTime: { type: Date, required: true },
ArrivalDateTime: { type: Date, required: true },
OriginAirportCode: { type: String, required: true, ref: 'Airport' },
DestinationAirportCode: { type: String, required: true, ref: 'Airport' },
AvailableSeats: { type: Number, required: true },
AirlineID: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Airline' }
});
module.exports = mongoose.model('Flight', FlightSchema);
This code snippet defines a Mongoose schema for a Flight
model in a Node.js application. Let’s go through it step by step:
Mongoose Import:
const mongoose = require('mongoose');
: This line imports the Mongoose library, which is an Object Data Modeling (ODM) library for MongoDB and Node.js. It facilitates interactions with MongoDB databases.
Flight Schema Definition:
const FlightSchema = new mongoose.Schema({ ... });
: Here, a Mongoose schema for theFlight
model is defined usingmongoose.Schema
.Schema Fields:
FlightNumber
: A string field representing the flight number. It is required (required: true
) and must be unique (unique: true
).DepartureDateTime
andArrivalDateTime
: Date fields representing the departure and arrival times of the flight. Both are required.OriginAirportCode
andDestinationAirportCode
: String fields representing the airport codes for the origin and destination airports of the flight. They are required and reference theAirport
model (assumingAirport
is defined elsewhere).AvailableSeats
: A number field representing the number of available seats on the flight. It is required.AirlineID
: An ObjectId field representing the ID of the airline operating the flight. It references theAirline
model (assumingAirline
is defined elsewhere). It is required.
Exporting the Model:
module.exports = mongoose.model('Flight', FlightSchema);
: This line exports the Mongoose model based on theFlightSchema
. Themongoose.model
method takes two arguments: the singular name of the collection that will be created for this model (‘Flight’ here), and the schema (FlightSchema
) that defines the structure of documents in that collection.
This schema definition sets up a Flight
model with specific fields and their respective data types and constraints. It leverages Mongoose to interact with a MongoDB database, providing structure and validation to the data. The ref
option in some fields (OriginAirportCode
, DestinationAirportCode
, AirlineID
) indicates that these fields reference documents in other collections (such as Airport
and Airline
), establishing relationships between different types of data in the database.
Passenger Model
const mongoose = require('mongoose');
const PassengerSchema = new mongoose.Schema({
FirstName: { type: String, required: true },
LastName: { type: String, required: true },
Email: { type: String, required: true, unique: true },
PassportNumber: { type: String, required: true, unique: true }
});
module.exports = mongoose.model('Passenger', PassengerSchema);
Explanation:
Mongoose Import:
const mongoose = require('mongoose');
: This imports the Mongoose library, allowing interaction with MongoDB databases and defining schemas.
Passenger Schema Definition:
const PassengerSchema = new mongoose.Schema({ ... });
: Here, a Mongoose schema for thePassenger
model is defined usingmongoose.Schema
.Schema Fields:
FirstName
andLastName
: These are string fields representing the first and last names of the passenger. Both are required (required: true
).Email
: A string field representing the email address of the passenger. It is required (required: true
) and must be unique (unique: true
).PassportNumber
: A string field representing the passport number of the passenger. It is required and must be unique (unique: true
).
Exporting the Model:
module.exports = mongoose.model('Passenger', PassengerSchema);
: This line exports the Mongoose model based on thePassengerSchema
. Themongoose.model
method takes two arguments: the singular name of the collection that will be created for this model (‘Passenger’ here), and the schema (PassengerSchema
) that defines the structure of documents in that collection.
This schema defines a Passenger
model with four fields: FirstName
, LastName
, Email
, and PassportNumber
. These fields are required and have unique constraints for Email
and PassportNumber
. When documents are saved using this schema, Mongoose will ensure that these validations are enforced before persisting data to the MongoDB database. This structure provides a clear definition of what constitutes a Passenger
object in the application, ensuring consistency and integrity of data stored in the database.
Airline Model
const mongoose = require('mongoose');
const AirlineSchema = new mongoose.Schema({
AirlineName: { type: String, required: true, unique: true },
ContactNumber: { type: String, required: true },
OperatingRegion: { type: String, required: true }
});
module.exports = mongoose.model('Airline', AirlineSchema);
Explanation:
Mongoose Import:
const mongoose = require('mongoose');
: This line imports the Mongoose library, allowing interaction with MongoDB databases and defining schemas.
Airline Schema Definition:
const AirlineSchema = new mongoose.Schema({ ... });
: Here, a Mongoose schema for theAirline
model is defined usingmongoose.Schema
.Schema Fields:
AirlineName
: A string field representing the name of the airline. It is required (required: true
) and must be unique (unique: true
).ContactNumber
: A string field representing the contact number of the airline. It is required (required: true
).OperatingRegion
: A string field representing the operating region of the airline. It is required (required: true
).
Exporting the Model:
module.exports = mongoose.model('Airline', AirlineSchema);
: This line exports the Mongoose model based on theAirlineSchema
. Themongoose.model
method takes two arguments: the singular name of the collection that will be created for this model (‘Airline’ here), and the schema (AirlineSchema
) that defines the structure of documents in that collection.
This schema definition sets up an Airline
model with three fields: AirlineName
, ContactNumber
, and OperatingRegion
. These fields are required and enforce uniqueness for AirlineName
. When documents are saved using this schema, Mongoose will validate that these fields are present and meet the specified requirements before persisting data to the MongoDB database. This structure provides a clear definition of what constitutes an Airline
object in the application, ensuring consistency and integrity of data stored in the database.
Airport Model
const mongoose = require('mongoose');
const AirportSchema = new mongoose.Schema({
AirportCode: { type: String, required: true, unique: true },
AirportName: { type: String, required: true },
Location: { type: String, required: true },
Facilities: { type: [String] }
});
module.exports = mongoose.model('Airport', AirportSchema);
Booking Model
const mongoose = require('mongoose');
const BookingSchema = new mongoose.Schema({
FlightID: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Flight' },
PassengerID: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Passenger' },
PaymentStatus: { type: String, required: true }
});
module.exports = mongoose.model('Booking', BookingSchema);
Payment Model
const mongoose = require('mongoose');
const PaymentSchema = new mongoose.Schema({
BookingID: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Booking' },
PaymentMethod: { type: String, required: true },
Amount: { type: Number, required: true },
TransactionDateTime: { type: Date, required: true }
});
module.exports = mongoose.model('Payment', PaymentSchema);
In this module, we have defined the database schema for the Flight Reservation System, including entities such as Flight, Passenger, Airline, Airport, Booking, and Payment. We also established the relationships between these entities. This foundational design will enable us to efficiently manage flight information, passenger details, airline data, airport information, bookings, and payments in our system. In the next module, we will focus on implementing user authentication and authorization.
Module 3: User Authentication and Authorization
Overview
In this module, we will implement user registration and login functionalities with error validation, full authorization, and authentication using JWT (JSON Web Tokens), bcrypt for password hashing, and cookies for session management. Additionally, we will create middleware for error handling and protecting routes.
Step-by-Step Guide
1. Install Additional Dependencies
We need to install a few additional npm packages for user authentication and authorization:
- bcrypt: For hashing passwords.
- jsonwebtoken: For generating and verifying JWTs.
- cookie-parser: For parsing cookies.
- express-validator: For request validation.
Install these packages:
npm install bcrypt jsonwebtoken cookie-parser express-validator
2. Create User Model
First, we need a User model to store user information.
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true }
});
module.exports = mongoose.model('User', UserSchema);
Explanation:
Mongoose Import:
const mongoose = require('mongoose');
: This imports the Mongoose library, which is used for MongoDB object modeling in Node.js.
User Schema Definition:
const UserSchema = new mongoose.Schema({ ... });
: Here, a Mongoose schema for theUser
model is defined usingmongoose.Schema
.Schema Fields:
username
: A string field representing the username of the user. It is required (required: true
) and must be unique (unique: true
).email
: A string field representing the email address of the user. It is required (required: true
) and must be unique (unique: true
).password
: A string field representing the password of the user. It is required (required: true
).
Exporting the Model:
module.exports = mongoose.model('User', UserSchema);
: This line exports the Mongoose model based on theUserSchema
. Themongoose.model
method takes two arguments: the singular name of the collection that will be created for this model (‘User’ here), and the schema (UserSchema
) that defines the structure of documents in that collection.
This schema definition sets up a User
model with three fields: username
, email
, and password
. Each field has specific requirements:
username
andemail
are required and must be unique across all documents in their respective collections.password
is required but does not have a uniqueness constraint since passwords are typically stored securely and not compared for uniqueness.
When documents are saved using this schema, Mongoose will validate that these fields are present and meet the specified requirements before persisting data to the MongoDB database. This structure provides a clear definition of what constitutes a User
object in the application, ensuring data integrity and security measures like unique usernames and emails.
3. Create Authentication Middleware
Next, we will create middleware for authentication, authorization, and error handling.
authMiddleware.js
const jwt = require('jsonwebtoken');
exports.authenticateToken = (req, res, next) => {
const token = req.cookies.token;
if (!token) return res.sendStatus(401);
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
};
exports.authorizeRoles = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ message: 'Access denied' });
}
next();
};
};
This code snippet provides two middleware functions commonly used in Node.js applications for handling authentication and authorization using JSON Web Tokens (JWTs):
1. authenticateToken
Middleware:
const jwt = require('jsonwebtoken');
exports.authenticateToken = (req, res, next) => {
const token = req.cookies.token;
if (!token) return res.sendStatus(401);
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
};
- Functionality:
authenticateToken
is a middleware function exported from this module.- It expects to find a JWT in a cookie named
token
in the incoming request (req.cookies.token
). - If the
token
is not present (!token
), it responds with a status code401
(Unauthorized). - If the token is present, it uses
jwt.verify
to decode and verify the token against theJWT_SECRET
stored in the environment variables (process.env.JWT_SECRET
). - If verification fails (
err
is not null), it responds with a status code403
(Forbidden). - If verification succeeds, it attaches the decoded user information (
user
) from the token toreq.user
and callsnext()
to pass control to the next middleware function in the request chain.
2. authorizeRoles
Middleware Generator:
exports.authorizeRoles = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ message: 'Access denied' });
}
next();
};
};
- Functionality:
authorizeRoles
is a higher-order function that takes multiple role parameters (...roles
) and returns another middleware function.- The returned middleware function checks if
req.user.role
(which should have been set byauthenticateToken
middleware) matches any of the roles passed toauthorizeRoles
. - If
req.user.role
does not match any of the specified roles, it responds with a status code403
(Forbidden) and sends a JSON response{ message: 'Access denied' }
. - If
req.user.role
matches one of the specified roles, it callsnext()
to pass control to the next middleware function in the request chain.
Usage:
These middleware functions can be used in route handlers to enforce authentication and authorization:
const { authenticateToken, authorizeRoles } = require('./middlewares/auth');
// Example route using authenticateToken and authorizeRoles middleware
app.get('/protected-route',
authenticateToken,
authorizeRoles('admin', 'manager'),
(req, res) => {
// Only accessible to users with roles 'admin' or 'manager'
res.json({ message: 'Access granted' });
}
);
authenticateToken
ensures that the request is authenticated by verifying the JWT token.authorizeRoles
checks if the authenticated user has the necessary role(s) to access a specific route or resource.
Together, these middleware functions help in implementing robust authentication and authorization mechanisms in Node.js applications using JWTs.
errorMiddleware.js
exports.errorHandler = (err, req, res, next) => {
const statusCode = res.statusCode !== 200 ? res.statusCode : 500;
res.status(statusCode);
res.json({
message: err.message,
stack: process.env.NODE_ENV === 'production' ? '🥞' : err.stack,
});
};
Explanation:
Function Signature:
exports.errorHandler = (err, req, res, next) => { ... }
: This defines an error handling middleware function that takes four parameters:err
(the error object),req
(the request object),res
(the response object), andnext
(the next middleware function).
Determining Status Code:
const statusCode = res.statusCode !== 200 ? res.statusCode : 500;
: This line determines the HTTP status code to use in the response. Ifres.statusCode
is not200 OK
, it usesres.statusCode
. Otherwise, it defaults to500 Internal Server Error
.
Setting Response Status:
res.status(statusCode);
: Sets the HTTP status code of the response based on thestatusCode
determined in the previous step.
Sending JSON Response:
res.json({ ... });
: Sends a JSON response with two properties:message
: The error message fromerr.message
.stack
: The stack trace of the error (err.stack
). However, ifprocess.env.NODE_ENV
is set to'production'
, it sends a generic message (in this case, an emoji ‘🥞’) instead of the actual stack trace to avoid leaking sensitive information.
Usage:
You typically use this errorHandler
middleware at the end of your middleware chain to catch any errors that may have occurred during request processing:
const { errorHandler } = require('./middlewares/errorHandler');
// Example usage in Express middleware chain
app.use('/api', apiRouter); // Mount your API routes
app.use(notFoundHandler); // Handle 404 errors
app.use(errorHandler); // Error handling middleware should be the last one
- It catches errors passed to
next(err)
from any middleware or route handler preceding it. - Depending on the error, it sets an appropriate HTTP status code (
statusCode
) and sends a JSON response with error details (message
and optionallystack
).
Notes:
- Ensure that
errorHandler
is defined after all other middleware and route handlers in your Express application to catch errors that may occur during request processing. - The handling of
err.stack
differs based on theNODE_ENV
environment variable:- In production (
NODE_ENV === 'production'
), it typically sends a more generic error response ('🥞'
), avoiding detailed error messages for security reasons. - In development or other environments, it includes the full stack trace (
err.stack
) for easier debugging.
- In production (
This errorHandler
function helps centralize error handling logic in your Node.js application, making error responses consistent and easier to manage across different parts of your codebase.
4. Create User Controller
We will create a controller to handle user registration and login.
userController.js
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const { validationResult } = require('express-validator');
exports.registerUser = async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { username, email, password } = req.body;
try {
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ message: 'User already exists' });
}
const hashedPassword = await bcrypt.hash(password, 10);
const user = new User({
username,
email,
password: hashedPassword,
});
await user.save();
res.status(201).json({ message: 'User registered successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.loginUser = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) {
return res.status(400).json({ message: 'Invalid email or password' });
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).json({ message: 'Invalid email or password' });
}
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, {
expiresIn: '1h',
});
res.cookie('token', token, { httpOnly: true });
res.status(200).json({ message: 'Logged in successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.logoutUser = (req, res) => {
res.clearCookie('token');
res.status(200).json({ message: 'Logged out successfully' });
};
This code snippet provides functions for user registration, login, and logout in a Node.js application using bcrypt for password hashing and JWT for authentication. Let’s break down each function:
1. registerUser
Function:
- Functionality:
- Validates the request body using
express-validator
middleware and checks for validation errors usingvalidationResult
. - Checks if a user with the provided email already exists in the database.
- If the user already exists, returns a
400
status with a JSON response indicating “User already exists”. - Hashes the password using
bcrypt.hash
with a salt round of10
. - Creates a new
User
instance with hashed password and saves it to the database usinguser.save()
. - Responds with a
201
status and a JSON response indicating “User registered successfully” upon successful registration. - Catches and handles any errors that occur during registration with a
500
status and a JSON response containing the error message.
- Validates the request body using
2. loginUser
Function:
- Functionality:
- Retrieves the
email
andpassword
from the request body. - Finds a user with the provided
email
in the database usingUser.findOne()
. - If no user is found, returns a
400
status with a JSON response indicating “Invalid email or password”. - Compares the provided
password
with the hashed password stored in the database usingbcrypt.compare()
. - If the passwords do not match, returns a
400
status with a JSON response indicating “Invalid email or password”. - If authentication is successful, generates a JWT (
token
) usingjwt.sign()
with the user’s_id
as the payload,JWT_SECRET
from environment variables, and an expiration of1 hour
. - Sets the JWT as a cookie named
token
withhttpOnly
flag totrue
. - Responds with a
200
status and a JSON response indicating “Logged in successfully” upon successful login. - Catches and handles any errors that occur during login with a
500
status and a JSON response containing the error message.
- Retrieves the
3. logoutUser
Function:
- Functionality:
- Clears the
token
cookie usingres.clearCookie('token')
. - Responds with a
200
status and a JSON response indicating “Logged out successfully”.
- Clears the
Usage Notes:
- Ensure that
bcrypt
andjsonwebtoken
packages are installed (npm install bcrypt jsonwebtoken
). - The
User
model should be defined in../models/User.js
or wherever appropriate in your project. express-validator
is used for request body validation. Ensure to define validation rules (not shown in the provided snippet) usingcheck()
orbody()
methods before handling the request in route handlers.- This code assumes you are using Express.js for your Node.js server.
These functions provide basic user authentication (registering and logging in) and logout functionality using bcrypt for password hashing and JWT for authentication tokens. They handle errors gracefully and provide appropriate responses to clients based on the outcome of the operations.
5. Create User Routes
Next, we will define the routes for user registration, login, and logout.
userRoutes.js
const express = require('express');
const { check } = require('express-validator');
const { registerUser, loginUser, logoutUser } = require('../controllers/userController');
const router = express.Router();
router.post(
'/register',
[
check('username', 'Username is required').not().isEmpty(),
check('email', 'Please include a valid email').isEmail(),
check('password', 'Password must be 6 or more characters').isLength({ min: 6 }),
],
registerUser
);
router.post(
'/login',
[
check('email', 'Please include a valid email').isEmail(),
check('password', 'Password is required').exists(),
],
loginUser
);
router.post('/logout', logoutUser);
module.exports = router;
This code sets up an Express router (router
) to handle user authentication endpoints (/register
, /login
, /logout
). It uses express-validator
for request validation and imports controller functions (registerUser
, loginUser
, logoutUser
) from ../controllers/userController
to handle the business logic. Let’s go through it step by step:
Explanation:
Express and Middleware Setup:
const express = require('express');
: Imports the Express framework.const { check } = require('express-validator');
: Imports thecheck
function fromexpress-validator
for input validation.
Router Initialization:
const router = express.Router();
: Creates an instance of an Express router to handle routing.
POST Endpoint for User Registration (
/register
):router.post('/register', [...], registerUser);
- Defines a POST route at
/register
. - Uses
express-validator
middleware to validate theusername
,email
, andpassword
fields. registerUser
is the controller function imported fromuserController
responsible for handling the registration process.
- Defines a POST route at
Validation Middleware for Registration:
[ check('username', 'Username is required').not().isEmpty(), check('email', 'Please include a valid email').isEmail(), check('password', 'Password must be 6 or more characters').isLength({ min: 6 }), ]
- Validates
username
to ensure it is not empty. - Validates
email
to ensure it is in a valid email format. - Validates
password
to ensure it is at least 6 characters long.
- Validates
POST Endpoint for User Login (
/login
):router.post('/login', [...], loginUser);
- Defines a POST route at
/login
. - Uses
express-validator
middleware to validate theemail
andpassword
fields. loginUser
is the controller function responsible for handling the login process.
- Defines a POST route at
Validation Middleware for Login:
[ check('email', 'Please include a valid email').isEmail(), check('password', 'Password is required').exists(), ]
- Validates
email
to ensure it is in a valid email format. - Ensures that
password
exists in the request body.
- Validates
POST Endpoint for User Logout (
/logout
):router.post('/logout', logoutUser);
- Defines a POST route at
/logout
. logoutUser
is the controller function responsible for handling the logout process.
- Defines a POST route at
Exporting the Router:
module.exports = router;
: Makes the router available for use in other parts of the application.
Usage:
- Ensure that
express
,express-validator
, and any other necessary middleware (body-parser
,cookie-parser
, etc.) are installed in your project (npm install express express-validator
). - Implement the controller functions (
registerUser
,loginUser
,logoutUser
) in../controllers/userController.js
to handle the actual registration, login, and logout logic. - Mount this router in your main application file (e.g.,
app.js
orindex.js
) usingapp.use('/api/users', userRouter);
assuminguserRouter
is imported from this module.
Notes:
- Error handling for validation errors is not shown in this snippet but should be handled either in the controller functions or with additional middleware.
- Always ensure to validate and sanitize input data to prevent security vulnerabilities such as SQL injection or cross-site scripting (XSS).
- This setup assumes you have structured your project with separation of concerns, where routes are defined separately from controllers to maintain a clean and organized codebase.
6. Integrate Middleware and Routes in the Server
Finally, we will integrate the middleware and routes into our server setup.
server.js
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const cookieParser = require('cookie-parser');
const userRoutes = require('./routes/userRoutes');
const { errorHandler } = require('./middleware/errorMiddleware');
dotenv.config();
const app = express();
const port = process.env.PORT || 5000;
app.use(express.json());
app.use(cookieParser());
app.use('/api/users', userRoutes);
app.use(errorHandler);
mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
}).then(() => {
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
}).catch(err => {
console.error('Connection error', err.message);
});
This code snippet sets up a basic Express server with MongoDB connection and middleware to handle user authentication routes. Let’s break down its components and functionality:
Components and Setup:
Imports:
const express = require('express');
: Imports the Express framework.const mongoose = require('mongoose');
: Imports Mongoose for MongoDB object modeling.const dotenv = require('dotenv');
: Importsdotenv
for loading environment variables from a.env
file.const cookieParser = require('cookie-parser');
: Importscookie-parser
middleware to parse cookies from incoming requests.const userRoutes = require('./routes/userRoutes');
: Imports user authentication routes defined in./routes/userRoutes
.const { errorHandler } = require('./middleware/errorMiddleware');
: Imports custom error handling middleware (errorHandler
) from./middleware/errorMiddleware
.
Environment Variables:
dotenv.config();
: Loads environment variables from a.env
file intoprocess.env
.
Express App Initialization:
const app = express();
: Creates an instance of Express application (app
).const port = process.env.PORT || 5000;
: Defines the port number from environment variables or defaults to5000
.
Middleware Setup:
app.use(express.json());
: Parses incoming requests with JSON payloads.app.use(cookieParser());
: Parses cookies attached to incoming requests.app.use('/api/users', userRoutes);
: Mounts user authentication routes under/api/users
.
Error Handling Middleware:
app.use(errorHandler);
: RegisterserrorHandler
middleware to handle errors passed down the middleware chain.
MongoDB Connection:
mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true })
: Connects to MongoDB using the URI specified inprocess.env.MONGO_URI
.- Uses options
useNewUrlParser: true
anduseUnifiedTopology: true
to ensure compatibility with the MongoDB Node.js driver.
Server Startup:
- Starts the Express server once MongoDB connection is established.
app.listen(port, () => { console.log(
Server running on port ${port}); });
: Listens on the specifiedport
and logs a message to indicate the server is running.
Usage Notes:
- Environment Variables: Ensure you have a
.env
file in the root directory of your project with necessary variables (PORT
,MONGO_URI
, etc.). - Middleware: Ensure all necessary middleware (
express.json()
,cookieParser()
) and error handling (errorHandler
) are correctly set up to handle requests and errors. - Routing: Define your user authentication routes (
userRoutes
) in./routes/userRoutes.js
or similar, handling user registration, login, logout, etc. - Database: Make sure MongoDB is running and accessible with the URI specified in
MONGO_URI
.
Example .env
File:
PORT=5000
MONGO_URI=mongodb://localhost:27017/mydatabase
JWT_SECRET=mysecretkey
Replace mongodb://localhost:27017/mydatabase
with your actual MongoDB URI, and mysecretkey
with your JWT secret key for token generation and verification.
In this module, we implemented user authentication and authorization using JWT, bcrypt, and cookies. We created middleware for authentication, authorization, and error handling. We also set up user registration, login, and logout functionalities with proper validation. In the next module, we will focus on creating models for other entities such as flights, passengers, airlines, airports, bookings, and payments.
Module 4: Models Creation
Overview
In this module, we will create Mongoose models for the various entities in our Flight Reservation System, including flights, passengers, airlines, airports, bookings, and payments. These models will help us interact with our MongoDB database and manage the data for our application.
Step-by-Step Guide
1. Flight Model
The Flight model will store information about individual flights, including flight numbers, departure and arrival times, origin and destination airports, available seats, and the airline operating the flight.
models/Flight.js
const mongoose = require('mongoose');
const FlightSchema = new mongoose.Schema({
FlightNumber: { type: String, required: true, unique: true },
DepartureDateTime: { type: Date, required: true },
ArrivalDateTime: { type: Date, required: true },
OriginAirportCode: { type: String, required: true, ref: 'Airport' },
DestinationAirportCode: { type: String, required: true, ref: 'Airport' },
AvailableSeats: { type: Number, required: true },
AirlineID: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Airline' }
});
module.exports = mongoose.model('Flight', FlightSchema);
Schema Fields:
FlightNumber:
- Type:
String
- Required:
true
- Unique:
true
- Purpose: Represents the unique identifier or number of the flight.
- Type:
DepartureDateTime:
- Type:
Date
- Required:
true
- Purpose: Stores the date and time when the flight is scheduled to depart.
- Type:
ArrivalDateTime:
- Type:
Date
- Required:
true
- Purpose: Stores the date and time when the flight is scheduled to arrive at its destination.
- Type:
OriginAirportCode:
- Type:
String
- Required:
true
- Reference:
Airport
- Purpose: Represents the code (e.g., IATA code) of the airport where the flight departs from. It references another model named
Airport
.
- Type:
DestinationAirportCode:
- Type:
String
- Required:
true
- Reference:
Airport
- Purpose: Represents the code (e.g., IATA code) of the airport where the flight arrives. It also references the
Airport
model.
- Type:
AvailableSeats:
- Type:
Number
- Required:
true
- Purpose: Indicates the number of seats available on the flight.
- Type:
AirlineID:
- Type:
mongoose.Schema.Types.ObjectId
- Required:
true
- Reference:
Airline
- Purpose: Stores the ObjectId of the airline operating the flight. It references another model named
Airline
.
- Type:
Relationships:
Airport Reference: Both
OriginAirportCode
andDestinationAirportCode
fields are referenced to theAirport
model, indicating relationships where each airport code is associated with an airport document in theAirport
collection.Airline Reference: The
AirlineID
field references theAirline
model, establishing a relationship where each flight is associated with an airline document in theAirline
collection.
Model Export:
module.exports = mongoose.model('Flight', FlightSchema);
This line exports the Mongoose model named 'Flight'
based on the FlightSchema
defined above. This model can now be used throughout your application to perform operations on flights stored in your MongoDB database.
2. Passenger Model
The Passenger model will store details about passengers, including their names, contact information, and passport numbers.
models/Passenger.js
const mongoose = require('mongoose');
const PassengerSchema = new mongoose.Schema({
FirstName: { type: String, required: true },
LastName: { type: String, required: true },
Email: { type: String, required: true, unique: true },
PassportNumber: { type: String, required: true, unique: true }
});
module.exports = mongoose.model('Passenger', PassengerSchema);
3. Airline Model
The Airline model will store information about airlines, including their names, contact details, and operating regions.
models/Airline.js
const mongoose = require('mongoose');
const AirlineSchema = new mongoose.Schema({
AirlineName: { type: String, required: true, unique: true },
ContactNumber: { type: String, required: true },
OperatingRegion: { type: String, required: true }
});
module.exports = mongoose.model('Airline', AirlineSchema);
4. Airport Model
The Airport model will store information about airports, including their codes, names, locations, and available facilities.
models/Airport.js
const mongoose = require('mongoose');
const AirportSchema = new mongoose.Schema({
AirportCode: { type: String, required: true, unique: true },
AirportName: { type: String, required: true },
Location: { type: String, required: true },
Facilities: { type: [String] }
});
module.exports = mongoose.model('Airport', AirportSchema);
5. Booking Model
The Booking model will handle flight reservations, including the flight details, passenger information, and payment status.
models/Booking.js
const mongoose = require('mongoose');
const BookingSchema = new mongoose.Schema({
FlightID: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Flight' },
PassengerID: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Passenger' },
PaymentStatus: { type: String, required: true }
});
module.exports = mongoose.model('Booking', BookingSchema);
6. Payment Model
The Payment model will store information about payments made for flight bookings, including transaction details and amounts.
models/Payment.js
const mongoose = require('mongoose');
const PaymentSchema = new mongoose.Schema({
BookingID: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Booking' },
PaymentMethod: { type: String, required: true },
Amount: { type: Number, required: true },
TransactionDateTime: { type: Date, required: true }
});
module.exports = mongoose.model('Payment', PaymentSchema);
In this module, we created Mongoose models for the key entities in our Flight Reservation System. These models include Flight, Passenger, Airline, Airport, Booking, and Payment. Each model defines the schema for its respective entity and sets up the necessary relationships between them. In the next module, we will implement controllers and routes to manage these entities and handle various operations in our application.
Module 5: Controllers and Routes Creation
Overview
In this module, we will create controllers and routes for managing the various entities in our Flight Reservation System, including flights, passengers, airlines, airports, bookings, and payments. The controllers will handle the business logic, while the routes will define the endpoints for our API.
Step-by-Step Guide
1. Flight Controller and Routes
controllers/flightController.js
const Flight = require('../models/Flight');
// Create a new flight
exports.createFlight = async (req, res) => {
try {
const flight = new Flight(req.body);
await flight.save();
res.status(201).json(flight);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get all flights
exports.getAllFlights = async (req, res) => {
try {
const flights = await Flight.find();
res.status(200).json(flights);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single flight by ID
exports.getFlightById = async (req, res) => {
try {
const flight = await Flight.findById(req.params.id);
if (!flight) {
return res.status(404).json({ message: 'Flight not found' });
}
res.status(200).json(flight);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update a flight by ID
exports.updateFlightById = async (req, res) => {
try {
const flight = await Flight.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!flight) {
return res.status(404).json({ message: 'Flight not found' });
}
res.status(200).json(flight);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete a flight by ID
exports.deleteFlightById = async (req, res) => {
try {
const flight = await Flight.findByIdAndDelete(req.params.id);
if (!flight) {
return res.status(404).json({ message: 'Flight not found' });
}
res.status(200).json({ message: 'Flight deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
The provided code snippet defines CRUD (Create, Read, Update, Delete) operations for a Flight
model in a Node.js application using Mongoose. Let’s go through each function and its purpose:
1. createFlight
Function:
// Create a new flight
exports.createFlight = async (req, res) => {
try {
const flight = new Flight(req.body); // Create a new Flight instance with data from req.body
await flight.save(); // Save the flight to the database
res.status(201).json(flight); // Respond with status 201 (Created) and the created flight object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles POST requests to create a new flight.
- Steps:
- Creates a new
Flight
instance using data fromreq.body
. - Saves the flight to the MongoDB database using
flight.save()
. - Responds with status
201
(Created) and the created flight object as JSON. - Catches and handles any errors that occur during the creation process, responding with a
500
status and an error message.
- Creates a new
2. getAllFlights
Function:
// Get all flights
exports.getAllFlights = async (req, res) => {
try {
const flights = await Flight.find(); // Retrieve all flights from the database
res.status(200).json(flights); // Respond with status 200 (OK) and the array of flights
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles GET requests to retrieve all flights.
- Steps:
- Uses
Flight.find()
to retrieve all flight documents from the MongoDB database. - Responds with status
200
(OK) and an array of flight objects as JSON. - Catches and handles any errors that occur during the retrieval process, responding with a
500
status and an error message.
- Uses
3. getFlightById
Function:
// Get a single flight by ID
exports.getFlightById = async (req, res) => {
try {
const flight = await Flight.findById(req.params.id); // Find a flight by its ID
if (!flight) {
return res.status(404).json({ message: 'Flight not found' }); // Handle case where flight is not found
}
res.status(200).json(flight); // Respond with status 200 (OK) and the found flight object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles GET requests to retrieve a single flight by its ID.
- Steps:
- Uses
Flight.findById(req.params.id)
to find a flight in the database based on the ID provided in the request parameters (req.params.id
). - Checks if the flight exists (
if (!flight)
), and if not, responds with status404
(Not Found) and a corresponding error message. - Responds with status
200
(OK) and the found flight object as JSON if the flight is found. - Catches and handles any errors that occur during the retrieval process, responding with a
500
status and an error message.
- Uses
4. updateFlightById
Function:
// Update a flight by ID
exports.updateFlightById = async (req, res) => {
try {
const flight = await Flight.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true }); // Find and update a flight by its ID
if (!flight) {
return res.status(404).json({ message: 'Flight not found' }); // Handle case where flight is not found
}
res.status(200).json(flight); // Respond with status 200 (OK) and the updated flight object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles PUT or PATCH requests to update a flight by its ID.
- Steps:
- Uses
Flight.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true })
to find and update a flight in the database based on the ID provided in the request parameters (req.params.id
).req.body
contains the updated data. new: true
ensures that the updated flight object is returned after the update operation.runValidators: true
ensures that Mongoose validators are run on the update operation.- Checks if the flight exists (
if (!flight)
), and if not, responds with status404
(Not Found) and a corresponding error message. - Responds with status
200
(OK) and the updated flight object as JSON if the update is successful. - Catches and handles any errors that occur during the update process, responding with a
500
status and an error message.
- Uses
5. deleteFlightById
Function:
// Delete a flight by ID
exports.deleteFlightById = async (req, res) => {
try {
const flight = await Flight.findByIdAndDelete(req.params.id); // Find and delete a flight by its ID
if (!flight) {
return res.status(404).json({ message: 'Flight not found' }); // Handle case where flight is not found
}
res.status(200).json({ message: 'Flight deleted successfully' }); // Respond with status 200 (OK) and a success message
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles DELETE requests to delete a flight by its ID.
- Steps:
- Uses
Flight.findByIdAndDelete(req.params.id)
to find and delete a flight in the database based on the ID provided in the request parameters (req.params.id
). - Checks if the flight exists (
if (!flight)
), and if not, responds with status404
(Not Found) and a corresponding error message. - Responds with status
200
(OK) and a success message indicating the flight was deleted successfully if the deletion is successful. - Catches and handles any errors that occur during the deletion process, responding with a
500
status and an error message.
- Uses
routes/flightRoutes.js
const express = require('express');
const {
createFlight,
getAllFlights,
getFlightById,
updateFlightById,
deleteFlightById
} = require('../controllers/flightController');
const { authenticateToken } = require('../middleware/authMiddleware');
const router = express.Router();
router.post('/', authenticateToken, createFlight);
router.get('/', getAllFlights);
router.get('/:id', getFlightById);
router.put('/:id', authenticateToken, updateFlightById);
router.delete('/:id', authenticateToken, deleteFlightById);
module.exports = router;
The provided code snippet sets up routing for handling Flight
resources in a Node.js application using Express. Let’s go through it step by step:
1. Importing Required Modules and Controllers
const express = require('express');
const {
createFlight,
getAllFlights,
getFlightById,
updateFlightById,
deleteFlightById
} = require('../controllers/flightController');
const { authenticateToken } = require('../middleware/authMiddleware');
const router = express.Router();
Modules and Controllers:
express
: Imports the Express framework.flightController
: Imports functions (createFlight
,getAllFlights
,getFlightById
,updateFlightById
,deleteFlightById
) from../controllers/flightController
. These functions are responsible for handling different HTTP requests related to flights.authMiddleware
: ImportsauthenticateToken
from../middleware/authMiddleware
. This middleware function ensures that routes requiring authentication are protected and only accessible with a valid token.
Router Initialization:
- Creates an instance of
express.Router()
and assigns it torouter
. This allows us to define middleware and routes specific to flights.
- Creates an instance of
2. Defining Routes
router.post('/', authenticateToken, createFlight);
router.get('/', getAllFlights);
router.get('/:id', getFlightById);
router.put('/:id', authenticateToken, updateFlightById);
router.delete('/:id', authenticateToken, deleteFlightById);
- Routes:
POST /
: Handles POST requests to create a new flight. It first callsauthenticateToken
middleware to ensure the request is authenticated, then callscreateFlight
function from the controller.GET /
: Handles GET requests to fetch all flights. CallsgetAllFlights
function from the controller.GET /:id
: Handles GET requests to fetch a single flight by its ID (:id
parameter). CallsgetFlightById
function from the controller.PUT /:id
: Handles PUT requests to update a flight by its ID. It first callsauthenticateToken
middleware to ensure the request is authenticated, then callsupdateFlightById
function from the controller.DELETE /:id
: Handles DELETE requests to delete a flight by its ID. It first callsauthenticateToken
middleware to ensure the request is authenticated, then callsdeleteFlightById
function from the controller.
3. Exporting the Router
module.exports = router;
- Export: Exports the
router
object so that it can be imported and used in other parts of the application, typically in the mainapp.js
orindex.js
file where all routes are consolidated.
2. Passenger Controller and Routes
controllers/passengerController.js
const Passenger = require('../models/Passenger');
// Create a new passenger
exports.createPassenger = async (req, res) => {
try {
const passenger = new Passenger(req.body);
await passenger.save();
res.status(201).json(passenger);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get all passengers
exports.getAllPassengers = async (req, res) => {
try {
const passengers = await Passenger.find();
res.status(200).json(passengers);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single passenger by ID
exports.getPassengerById = async (req, res) => {
try {
const passenger = await Passenger.findById(req.params.id);
if (!passenger) {
return res.status(404).json({ message: 'Passenger not found' });
}
res.status(200).json(passenger);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update a passenger by ID
exports.updatePassengerById = async (req, res) => {
try {
const passenger = await Passenger.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!passenger) {
return res.status(404).json({ message: 'Passenger not found' });
}
res.status(200).json(passenger);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete a passenger by ID
exports.deletePassengerById = async (req, res) => {
try {
const passenger = await Passenger.findByIdAndDelete(req.params.id);
if (!passenger) {
return res.status(404).json({ message: 'Passenger not found' });
}
res.status(200).json({ message: 'Passenger deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
The provided code snippet defines CRUD (Create, Read, Update, Delete) operations for a Passenger
model in a Node.js application using Mongoose. Let’s review each function and its purpose:
1. createPassenger
Function:
// Create a new passenger
exports.createPassenger = async (req, res) => {
try {
const passenger = new Passenger(req.body); // Create a new Passenger instance with data from req.body
await passenger.save(); // Save the passenger to the database
res.status(201).json(passenger); // Respond with status 201 (Created) and the created passenger object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles POST requests to create a new passenger.
- Steps:
- Creates a new
Passenger
instance using data fromreq.body
. - Saves the passenger to the MongoDB database using
passenger.save()
. - Responds with status
201
(Created) and the created passenger object as JSON. - Catches and handles any errors that occur during the creation process, responding with a
500
status and an error message.
- Creates a new
2. getAllPassengers
Function:
// Get all passengers
exports.getAllPassengers = async (req, res) => {
try {
const passengers = await Passenger.find(); // Retrieve all passengers from the database
res.status(200).json(passengers); // Respond with status 200 (OK) and the array of passengers
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles GET requests to retrieve all passengers.
- Steps:
- Uses
Passenger.find()
to retrieve all passenger documents from the MongoDB database. - Responds with status
200
(OK) and an array of passenger objects as JSON. - Catches and handles any errors that occur during the retrieval process, responding with a
500
status and an error message.
- Uses
3. getPassengerById
Function:
// Get a single passenger by ID
exports.getPassengerById = async (req, res) => {
try {
const passenger = await Passenger.findById(req.params.id); // Find a passenger by its ID
if (!passenger) {
return res.status(404).json({ message: 'Passenger not found' }); // Handle case where passenger is not found
}
res.status(200).json(passenger); // Respond with status 200 (OK) and the found passenger object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles GET requests to retrieve a single passenger by its ID.
- Steps:
- Uses
Passenger.findById(req.params.id)
to find a passenger in the database based on the ID provided in the request parameters (req.params.id
). - Checks if the passenger exists (
if (!passenger)
), and if not, responds with status404
(Not Found) and a corresponding error message. - Responds with status
200
(OK) and the found passenger object as JSON if the passenger is found. - Catches and handles any errors that occur during the retrieval process, responding with a
500
status and an error message.
- Uses
4. updatePassengerById
Function:
// Update a passenger by ID
exports.updatePassengerById = async (req, res) => {
try {
const passenger = await Passenger.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!passenger) {
return res.status(404).json({ message: 'Passenger not found' }); // Handle case where passenger is not found
}
res.status(200).json(passenger); // Respond with status 200 (OK) and the updated passenger object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles PUT or PATCH requests to update a passenger by its ID.
- Steps:
- Uses
Passenger.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true })
to find and update a passenger in the database based on the ID provided in the request parameters (req.params.id
).req.body
contains the updated data. new: true
ensures that the updated passenger object is returned after the update operation.runValidators: true
ensures that Mongoose validators are run on the update operation.- Checks if the passenger exists (
if (!passenger)
), and if not, responds with status404
(Not Found) and a corresponding error message. - Responds with status
200
(OK) and the updated passenger object as JSON if the update is successful. - Catches and handles any errors that occur during the update process, responding with a
500
status and an error message.
- Uses
5. deletePassengerById
Function:
// Delete a passenger by ID
exports.deletePassengerById = async (req, res) => {
try {
const passenger = await Passenger.findByIdAndDelete(req.params.id); // Find and delete a passenger by its ID
if (!passenger) {
return res.status(404).json({ message: 'Passenger not found' }); // Handle case where passenger is not found
}
res.status(200).json({ message: 'Passenger deleted successfully' }); // Respond with status 200 (OK) and a success message
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles DELETE requests to delete a passenger by its ID.
- Steps:
- Uses
Passenger.findByIdAndDelete(req.params.id)
to find and delete a passenger in the database based on the ID provided in the request parameters (req.params.id
). - Checks if the passenger exists (
if (!passenger)
), and if not, responds with status404
(Not Found) and a corresponding error message. - Responds with status
200
(OK) and a success message indicating the passenger was deleted successfully if the deletion is successful. - Catches and handles any errors that occur during the deletion process, responding with a
500
status and an error message.
- Uses
routes/passengerRoutes.js
const express = require('express');
const {
createPassenger,
getAllPassengers,
getPassengerById,
updatePassengerById,
deletePassengerById
} = require('../controllers/passengerController');
const { authenticateToken } = require('../middleware/authMiddleware');
const router = express.Router();
router.post('/', authenticateToken, createPassenger);
router.get('/', getAllPassengers);
router.get('/:id', getPassengerById);
router.put('/:id', authenticateToken, updatePassengerById);
router.delete('/:id', authenticateToken, deletePassengerById);
module.exports = router;
3. Airline Controller and Routes
controllers/airlineController.js
const Airline = require('../models/Airline');
// Create a new airline
exports.createAirline = async (req, res) => {
try {
const airline = new Airline(req.body);
await airline.save();
res.status(201).json(airline);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get all airlines
exports.getAllAirlines = async (req, res) => {
try {
const airlines = await Airline.find();
res.status(200).json(airlines);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single airline by ID
exports.getAirlineById = async (req, res) => {
try {
const airline = await Airline.findById(req.params.id);
if (!airline) {
return res.status(404).json({ message: 'Airline not found' });
}
res.status(200).json(airline);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update an airline by ID
exports.updateAirlineById = async (req, res) => {
try {
const airline = await Airline.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!airline) {
return res.status(404).json({ message: 'Airline not found' });
}
res.status(200).json(airline);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete an airline by ID
exports.deleteAirlineById = async (req, res) => {
try {
const airline = await Airline.findByIdAndDelete(req.params.id);
if (!airline) {
return res.status(404).json({ message: 'Airline not found' });
}
res.status(200).json({ message: 'Airline deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
routes/airlineRoutes.js
const express = require('express');
const {
createAirline,
getAllAirlines,
getAirlineById,
updateAirlineById,
deleteAirlineById
} = require('../controllers/airlineController');
const { authenticateToken } = require('../middleware/authMiddleware');
const router = express.Router();
router.post('/', authenticateToken, createAirline);
router.get('/', getAllAirlines);
router.get('/:id', getAirlineById);
router.put('/:id', authenticateToken, updateAirlineById);
router.delete('/:id', authenticateToken, deleteAirlineById);
module.exports = router;
4. Airport Controller and Routes
controllers/airportController.js
const Airport = require('../models/Airport');
// Create a new airport
exports.createAirport = async (req, res) => {
try {
const airport = new Airport(req.body);
await airport.save();
res.status(201).json(airport);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get all airports
exports.getAllAirports = async (req, res) => {
try {
const airports = await Airport.find();
res.status(200).json(airports);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single airport by code
exports.getAirportByCode = async (req, res) => {
try {
const airport = await Airport.findOne({ AirportCode: req.params.code });
if (!airport) {
return res.status(404).json({ message: 'Airport not found' });
}
res.status(200).json(airport);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update an airport by code
exports.updateAirportByCode = async (req, res) => {
try {
const airport = await Airport.findOneAndUpdate({ AirportCode: req.params.code }, req.body, { new: true, runValidators: true });
if (!airport) {
return res.status(404).json({ message: 'Airport not found' });
}
res.status(200).json(airport);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete an airport by code
exports.deleteAirportByCode = async (req, res) => {
try {
const airport = await Airport.findOneAndDelete({ AirportCode: req.params.code });
if (!airport) {
return res.status(404).json({ message: 'Airport not found' });
}
res.status(200).json({ message: 'Airport deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
routes/airportRoutes.js
const express = require('express');
const {
createAirport,
getAllAirports,
getAirportByCode,
updateAirportByCode,
deleteAirportByCode
} = require('../controllers/airportController');
const { authenticateToken } = require('../middleware/authMiddleware');
const router = express.Router();
router.post('/', authenticateToken, createAirport);
router.get('/', getAllAirports);
router.get('/:code', getAirportByCode);
router.put('/:code', authenticateToken, updateAirportByCode);
router.delete('/:code', authenticateToken, deleteAirportByCode);
module.exports = router;
5. Booking Controller and Routes
controllers/bookingController.js
const Booking = require('../models/Booking');
// Create a new booking
exports.createBooking = async (req, res) => {
try {
const booking = new Booking(req.body);
await booking.save();
res.status(201).json(booking);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get all bookings
exports.getAllBookings = async (req, res) => {
try {
const bookings = await Booking.find().populate('FlightID PassengerID');
res.status(200).json(bookings);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single booking by ID
exports.getBookingById = async (req, res) => {
try {
const booking = await Booking.findById(req.params.id).populate('FlightID PassengerID');
if (!booking) {
return res.status(404).json({ message: 'Booking not found' });
}
res.status(200).json(booking);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update a booking by ID
exports.updateBookingById = async (req, res) => {
try {
const booking = await Booking.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!booking) {
return res.status(404).json({ message: 'Booking not found' });
}
res.status(200).json(booking);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete a booking by ID
exports.deleteBookingById = async (req, res) => {
try {
const booking = await Booking.findByIdAndDelete(req.params.id);
if (!booking) {
return res.status(404).json({ message: 'Booking not found' });
}
res.status(200).json({ message: 'Booking deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
routes/bookingRoutes.js
const express = require('express');
const {
createBooking,
getAllBookings,
getBookingById,
updateBookingById,
deleteBookingById
} = require('../controllers/bookingController');
const { authenticateToken } = require('../middleware/authMiddleware');
const router = express.Router();
router.post('/', authenticateToken, createBooking);
router.get('/', authenticateToken, getAllBookings);
router.get('/:id', authenticateToken, getBookingById);
router.put('/:id', authenticateToken, updateBookingById);
router.delete('/:id', authenticateToken, deleteBookingById);
module.exports = router;
6. Payment Controller and Routes
controllers/paymentController.js
const Payment = require('../models/Payment');
// Create a new payment
exports.createPayment = async (req, res) => {
try {
const payment = new Payment(req.body);
await payment.save();
res.status(201).json(payment);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get all payments
exports.getAllPayments = async (req, res) => {
try {
const payments = await Payment.find().populate('BookingID');
res.status(200).json(payments);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single payment by ID
exports.getPaymentById = async (req, res) => {
try {
const payment = await Payment.findById(req.params.id).populate('BookingID');
if (!payment) {
return res.status(404).json({ message: 'Payment not found' });
}
res.status(200).json(payment);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update a payment by ID
exports.updatePaymentById = async (req, res) => {
try {
const payment = await Payment.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!payment) {
return res.status(404).json({ message: 'Payment not found' });
}
res.status(200).json(payment);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete a payment by ID
exports.deletePaymentById = async (req, res) => {
try {
const payment = await Payment.findByIdAndDelete(req.params.id);
if (!payment) {
return res.status(404).json({ message: 'Payment not found' });
}
res.status(200).json({ message: 'Payment deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
The provided code snippet defines CRUD (Create, Read, Update, Delete) operations for a Payment
model in a Node.js application using Mongoose. Let’s break down each function and its purpose:
1. createPayment
Function:
// Create a new payment
exports.createPayment = async (req, res) => {
try {
const payment = new Payment(req.body); // Create a new Payment instance with data from req.body
await payment.save(); // Save the payment to the database
res.status(201).json(payment); // Respond with status 201 (Created) and the created payment object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles POST requests to create a new payment.
- Steps:
- Creates a new
Payment
instance using data fromreq.body
. - Saves the payment to the MongoDB database using
payment.save()
. - Responds with status
201
(Created) and the created payment object as JSON. - Catches and handles any errors that occur during the creation process, responding with a
500
status and an error message.
- Creates a new
2. getAllPayments
Function:
// Get all payments
exports.getAllPayments = async (req, res) => {
try {
const payments = await Payment.find().populate('BookingID'); // Retrieve all payments from the database and populate the 'BookingID' field
res.status(200).json(payments); // Respond with status 200 (OK) and the array of payments
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles GET requests to retrieve all payments.
- Steps:
- Uses
Payment.find().populate('BookingID')
to retrieve all payment documents from the MongoDB database. The.populate('BookingID')
method populates theBookingID
field with details from the referenced model (assumingBookingID
is a reference field). - Responds with status
200
(OK) and an array of payment objects as JSON. - Catches and handles any errors that occur during the retrieval process, responding with a
500
status and an error message.
- Uses
3. getPaymentById
Function:
// Get a single payment by ID
exports.getPaymentById = async (req, res) => {
try {
const payment = await Payment.findById(req.params.id).populate('BookingID'); // Find a payment by its ID and populate the 'BookingID' field
if (!payment) {
return res.status(404).json({ message: 'Payment not found' }); // Handle case where payment is not found
}
res.status(200).json(payment); // Respond with status 200 (OK) and the found payment object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles GET requests to retrieve a single payment by its ID.
- Steps:
- Uses
Payment.findById(req.params.id).populate('BookingID')
to find a payment in the database based on the ID provided in the request parameters (req.params.id
). The.populate('BookingID')
method populates theBookingID
field with details from the referenced model (assumingBookingID
is a reference field). - Checks if the payment exists (
if (!payment)
), and if not, responds with status404
(Not Found) and a corresponding error message. - Responds with status
200
(OK) and the found payment object as JSON if the payment is found. - Catches and handles any errors that occur during the retrieval process, responding with a
500
status and an error message.
- Uses
4. updatePaymentById
Function:
// Update a payment by ID
exports.updatePaymentById = async (req, res) => {
try {
const payment = await Payment.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!payment) {
return res.status(404).json({ message: 'Payment not found' }); // Handle case where payment is not found
}
res.status(200).json(payment); // Respond with status 200 (OK) and the updated payment object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles PUT or PATCH requests to update a payment by its ID.
- Steps:
- Uses
Payment.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true })
to find and update a payment in the database based on the ID provided in the request parameters (req.params.id
).req.body
contains the updated data. new: true
ensures that the updated payment object is returned after the update operation.runValidators: true
ensures that Mongoose validators are run on the update operation.- Checks if the payment exists (
if (!payment)
), and if not, responds with status404
(Not Found) and a corresponding error message. - Responds with status
200
(OK) and the updated payment object as JSON if the update is successful. - Catches and handles any errors that occur during the update process, responding with a
500
status and an error message.
- Uses
5. deletePaymentById
Function:
// Delete a payment by ID
exports.deletePaymentById = async (req, res) => {
try {
const payment = await Payment.findByIdAndDelete(req.params.id); // Find and delete a payment by its ID
if (!payment) {
return res.status(404).json({ message: 'Payment not found' }); // Handle case where payment is not found
}
res.status(200).json({ message: 'Payment deleted successfully' }); // Respond with status 200 (OK) and a success message
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors and respond with a 500 status
}
};
- Purpose: Handles DELETE requests to delete a payment by its ID.
- Steps:
- Uses
Payment.findByIdAndDelete(req.params.id)
to find and delete a payment in the database based on the ID provided in the request parameters (req.params.id
). - Checks if the payment exists (
if (!payment)
), and if not, responds with status404
(Not Found) and a corresponding error message. - Responds with status
200
(OK) and a success message indicating the payment was deleted successfully if the deletion is successful. - Catches and handles any errors that occur during the deletion process, responding with a
500
status and an error message.
- Uses
routes/paymentRoutes.js
const express = require('express');
const {
createPayment,
getAllPayments,
getPaymentById,
updatePaymentById,
deletePaymentById
} = require('../controllers/paymentController');
const { authenticateToken } = require('../middleware/authMiddleware');
const router = express.Router();
router.post('/', authenticateToken, createPayment);
router.get('/', authenticateToken, getAllPayments);
router.get('/:id', authenticateToken, getPaymentById);
router.put('/:id', authenticateToken, updatePaymentById);
router.delete('/:id', authenticateToken, deletePaymentById);
module.exports = router;
7. Integrate Routes into the Server
Finally, integrate the created routes into our server setup.
server.js
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const cookieParser = require('cookie-parser');
const userRoutes = require('./routes/userRoutes');
const flightRoutes = require('./routes/flightRoutes');
const passengerRoutes = require('./routes/passengerRoutes');
const airlineRoutes = require('./routes/airlineRoutes');
const airportRoutes = require('./routes/airportRoutes');
const bookingRoutes = require('./routes/bookingRoutes');
const paymentRoutes = require('./routes/paymentRoutes');
const { errorHandler } = require('./middleware/errorMiddleware');
dotenv.config();
const app = express();
const port = process.env.PORT || 5000;
app.use(express.json());
app.use(cookieParser());
app.use('/api/users', userRoutes);
app.use('/api/flights', flightRoutes);
app.use('/api/passengers', passengerRoutes);
app.use('/api/airlines', airlineRoutes);
app.use('/api/airports', airportRoutes);
app.use('/api/bookings', bookingRoutes);
app.use('/api/payments', paymentRoutes);
app.use(errorHandler);
mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
}).then(() => {
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
}).catch(err => {
console.error('Connection error', err.message);
});
The provided code sets up a Node.js Express server with MongoDB using Mongoose for an aviation-related application. Let’s break down the key components and their functionalities:
1. Imports and Configuration
- Express: Web framework for Node.js.
- Mongoose: MongoDB object modeling tool.
- dotenv: Loads environment variables from a
.env
file intoprocess.env
. - cookie-parser: Middleware to parse cookies attached to the client request.
- Route Modules: Imported route modules for different entities (
userRoutes
,flightRoutes
,passengerRoutes
,airlineRoutes
,airportRoutes
,bookingRoutes
,paymentRoutes
). - errorMiddleware: Custom error handler middleware.
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const cookieParser = require('cookie-parser');
const userRoutes = require('./routes/userRoutes');
const flightRoutes = require('./routes/flightRoutes');
const passengerRoutes = require('./routes/passengerRoutes');
const airlineRoutes = require('./routes/airlineRoutes');
const airportRoutes = require('./routes/airportRoutes');
const bookingRoutes = require('./routes/bookingRoutes');
const paymentRoutes = require('./routes/paymentRoutes');
const { errorHandler } = require('./middleware/errorMiddleware');
dotenv.config();
2. Express App Setup
- Creates an instance of Express (
app
). - Sets the port from environment variables or defaults to port
5000
. - Middleware setup:
express.json()
: Parses incoming request bodies in JSON format.cookieParser()
: Parses cookies attached to the client request.
const app = express();
const port = process.env.PORT || 5000;
app.use(express.json());
app.use(cookieParser());
3. Routing
- Mounts route handlers for different API endpoints:
/api/users
: User-related routes./api/flights
: Flight-related routes./api/passengers
: Passenger-related routes./api/airlines
: Airline-related routes./api/airports
: Airport-related routes./api/bookings
: Booking-related routes./api/payments
: Payment-related routes.
app.use('/api/users', userRoutes);
app.use('/api/flights', flightRoutes);
app.use('/api/passengers', passengerRoutes);
app.use('/api/airlines', airlineRoutes);
app.use('/api/airports', airportRoutes);
app.use('/api/bookings', bookingRoutes);
app.use('/api/payments', paymentRoutes);
4. Error Handling
- Uses
errorHandler
middleware to catch and handle errors. - Responds with appropriate status codes and error messages.
app.use(errorHandler);
5. MongoDB Connection
- Connects to MongoDB using Mongoose.
- Uses
process.env.MONGO_URI
to get the MongoDB connection string. - Options
useNewUrlParser
anduseUnifiedTopology
are passed tomongoose.connect()
for connection configuration. - Starts the Express server on the specified port after successful MongoDB connection.
mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
}).then(() => {
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
}).catch(err => {
console.error('Connection error', err.message);
});
This setup creates a robust Node.js Express server that handles various API endpoints related to users, flights, passengers, airlines, airports, bookings, and payments. It uses MongoDB for data storage via Mongoose, handles JSON parsing and cookie management, and includes error handling to ensure reliable operation. Adjustments and improvements can be made based on specific application requirements and deployment environments.
In this module, we created controllers and routes for managing flights, passengers, airlines, airports, bookings, and payments in our Flight Reservation System. The controllers handle the business logic, while the routes define the API endpoints. In the next module, we will focus on implementing advanced features such as pagination, filtering, and search functionalities.
Module 6: Advanced Pagination, Filtering, and Search
Overview
In this module, we will implement advanced features such as pagination, filtering, and search functionalities within the controllers of our Flight Reservation System. This will enhance the user experience by allowing more efficient data retrieval and navigation.
In the context of Mongoose and Node.js, filtering allows you to retrieve documents from MongoDB based on specified criteria. Mongoose provides a rich set of methods and operators to construct complex queries easily.
Basic Filtering with Mongoose
When you want to retrieve documents from a MongoDB collection that meet certain conditions, you typically define a filter object that specifies these conditions. Here’s how you can construct and use filters with Mongoose:
Example Schema
Let’s assume you have a Mongoose schema defined for flights, similar to:
const mongoose = require('mongoose');
const flightSchema = new mongoose.Schema({
FlightNumber: String,
OriginAirportCode: String,
DestinationAirportCode: String,
DepartureDateTime: Date,
// Other fields…
});
const Flight = mongoose.model(‘Flight’, flightSchema);
module.exports = Flight;
Example Filter
Let’s break down how you might construct a filter using Mongoose’s methods and MongoDB operators:
const Flight = require('../models/Flight');
// Example filter object
const filter = {
OriginAirportCode: ‘JFK’, // Filter by origin airport code
DestinationAirportCode: ‘LAX’, // Filter by destination airport code
DepartureDateTime: {
$gte: new Date(‘2023-07-01T00:00:00Z’), // Filter flights departing on or after July 1, 2023
$lt: new Date(‘2023-07-02T00:00:00Z’) // Filter flights departing before July 2, 2023
},
FlightNumber: { $regex: ‘123’, $options: ‘i’ } // Filter flights where FlightNumber contains ‘123’ case-insensitive
};
Using Filters in Queries
You can use the filter object with Mongoose query methods (find
, findOne
, findById
, etc.) to retrieve documents from MongoDB:
// Find flights based on the filter criteria
Flight.find(filter)
.limit(10) // Limit the number of results
.skip(0) // Skip the first N results (useful for pagination)
.sort({ DepartureDateTime: 1 }) // Sort results by departure datetime ascending
.exec((err, flights) => {
if (err) {
console.error(err);
// Handle error
} else {
console.log(flights);
// Process retrieved flights
}
});
Explanation of the Filter Object
Exact Matches: Properties like
OriginAirportCode
andDestinationAirportCode
in the filter object match documents where these fields exactly match the specified values ('JFK'
and'LAX'
, respectively).Date Range: The
DepartureDateTime
property uses MongoDB’s$gte
(greater than or equal to) and$lt
(less than) operators to filter flights departing on or after July 1, 2023, and before July 2, 2023.Regex Search: The
FlightNumber
field uses MongoDB’s$regex
operator with thei
option ($options: 'i'
) for case-insensitive matching. This filters flights where theFlightNumber
field contains'123'
.
Special Considerations
Combining Filters: You can combine multiple filters using logical operators like
$and
,$or
, and others provided by MongoDB to create more complex queries.Handling Dates: Ensure that dates are handled consistently and correctly, especially when comparing or filtering based on date ranges.
Performance: Using indexes on fields frequently used in filters (
OriginAirportCode
,DestinationAirportCode
, etc.) can significantly improve query performance.
In summary, filtering with Mongoose and MongoDB involves constructing a filter object that specifies criteria for selecting documents from a collection. Mongoose provides methods like find
, findOne
, and findById
to execute queries based on these filters, leveraging MongoDB’s powerful querying capabilities. This approach allows you to retrieve specific subsets of data from your database based on application requirements, enhancing flexibility and efficiency in handling data retrieval operations. Adjust filter criteria and query options as per your application’s needs and data schema.
Step-by-Step Guide
1. Flight Controller with Pagination, Filtering, and Search
controllers/flightController.js
const Flight = require('../models/Flight');
// Create a new flight
exports.createFlight = async (req, res) => {
try {
const flight = new Flight(req.body);
await flight.save();
res.status(201).json(flight);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get all flights with pagination, filtering, and search
exports.getAllFlights = async (req, res) => {
const { page = 1, limit = 10, search, origin, destination, date } = req.query;
const filter = {};
if (search) {
filter.$or = [
{ FlightNumber: { $regex: search, $options: 'i' } },
{ 'OriginAirportCode': { $regex: search, $options: 'i' } },
{ 'DestinationAirportCode': { $regex: search, $options: 'i' } },
];
}
if (origin) filter.OriginAirportCode = origin;
if (destination) filter.DestinationAirportCode = destination;
if (date) {
const dateObj = new Date(date);
filter.DepartureDateTime = {
$gte: dateObj,
$lt: new Date(dateObj.getTime() + 24 * 60 * 60 * 1000)
};
}
try {
const flights = await Flight.find(filter)
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
const count = await Flight.countDocuments(filter);
res.status(200).json({
flights,
totalPages: Math.ceil(count / limit),
currentPage: page
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single flight by ID
exports.getFlightById = async (req, res) => {
try {
const flight = await Flight.findById(req.params.id);
if (!flight) {
return res.status(404).json({ message: 'Flight not found' });
}
res.status(200).json(flight);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update a flight by ID
exports.updateFlightById = async (req, res) => {
try {
const flight = await Flight.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!flight) {
return res.status(404).json({ message: 'Flight not found' });
}
res.status(200).json(flight);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete a flight by ID
exports.deleteFlightById = async (req, res) => {
try {
const flight = await Flight.findByIdAndDelete(req.params.id);
if (!flight) {
return res.status(404).json({ message: 'Flight not found' });
}
res.status(200).json({ message: 'Flight deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
The provided code defines CRUD (Create, Read, Update, Delete) operations for the Flight
model in a Node.js Express application using Mongoose. Let’s break down each function:
1. Create a New Flight (createFlight
)
// Create a new flight
exports.createFlight = async (req, res) => {
try {
const flight = new Flight(req.body); // Create a new Flight object using req.body data
await flight.save(); // Save the flight to the database
res.status(201).json(flight); // Respond with the created flight object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Handles POST requests to create a new flight.
- Steps:
- Creates a new instance of the
Flight
model using data fromreq.body
. - Saves the flight to the MongoDB database using
flight.save()
. - Responds with status 201 (Created) and the JSON representation of the created flight.
- Catches and handles any errors that occur during the creation process.
- Creates a new instance of the
2. Get All Flights with Pagination, Filtering, and Search (getAllFlights
)
// Get all flights with pagination, filtering, and search
exports.getAllFlights = async (req, res) => {
const { page = 1, limit = 10, search, origin, destination, date } = req.query;
const filter = {};
if (search) {
filter.$or = [
{ FlightNumber: { $regex: search, $options: 'i' } },
{ 'OriginAirportCode': { $regex: search, $options: 'i' } },
{ 'DestinationAirportCode': { $regex: search, $options: 'i' } },
];
}
if (origin) filter.OriginAirportCode = origin;
if (destination) filter.DestinationAirportCode = destination;
if (date) {
const dateObj = new Date(date);
filter.DepartureDateTime = {
$gte: dateObj,
$lt: new Date(dateObj.getTime() + 24 * 60 * 60 * 1000)
};
}
try {
const flights = await Flight.find(filter)
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
const count = await Flight.countDocuments(filter);
res.status(200).json({
flights,
totalPages: Math.ceil(count / limit),
currentPage: page
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
Functionality: Retrieves flights with support for pagination, filtering, and search.
Query Parameters:
page
,limit
: Pagination parameters to control the number of results per page and current page.search
: Searches for flights based on flight number, origin airport code, or destination airport code.origin
,destination
: Filters flights by origin or destination airport code.date
: Filters flights based on departure date.
Steps:
- Constructs a
filter
object based on provided query parameters to support flexible search criteria. - Uses Mongoose’s
find()
method withlimit
,skip
, andexec()
for pagination. - Counts total documents matching the filter criteria for calculating total pages.
- Responds with status 200 (OK) and JSON containing flights array, total pages, and current page.
- Catches and handles any errors that occur during the query execution.
- Constructs a
3. Get a Single Flight by ID (getFlightById
)
// Get a single flight by ID
exports.getFlightById = async (req, res) => {
try {
const flight = await Flight.findById(req.params.id); // Find a flight by its ID
if (!flight) {
return res.status(404).json({ message: 'Flight not found' }); // Handle case where flight is not found
}
res.status(200).json(flight); // Respond with the found flight object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Retrieves a single flight based on its ID.
- Steps:
- Uses Mongoose’s
findById()
method to find a flight by its MongoDB document ID (req.params.id
). - Checks if the flight exists (
flight !== null
), if not, responds with status 404 (Not Found). - Responds with status 200 (OK) and JSON containing the flight object.
- Catches and handles any errors that occur during the query execution.
- Uses Mongoose’s
4. Update a Flight by ID (updateFlightById
)
// Update a flight by ID
exports.updateFlightById = async (req, res) => {
try {
const flight = await Flight.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!flight) {
return res.status(404).json({ message: 'Flight not found' }); // Handle case where flight is not found
}
res.status(200).json(flight); // Respond with the updated flight object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Updates a flight based on its ID.
- Steps:
- Uses Mongoose’s
findByIdAndUpdate()
method to find and update a flight by its ID (req.params.id
). req.body
contains the updated flight data.- Options
{ new: true, runValidators: true }
ensures the updated document is returned and runs validators on update. - Checks if the flight exists (
flight !== null
), if not, responds with status 404 (Not Found). - Responds with status 200 (OK) and JSON containing the updated flight object.
- Catches and handles any errors that occur during the update operation.
- Uses Mongoose’s
5. Delete a Flight by ID (deleteFlightById
)
// Delete a flight by ID
exports.deleteFlightById = async (req, res) => {
try {
const flight = await Flight.findByIdAndDelete(req.params.id); // Find and delete a flight by its ID
if (!flight) {
return res.status(404).json({ message: 'Flight not found' }); // Handle case where flight is not found
}
res.status(200).json({ message: 'Flight deleted successfully' }); // Respond with success message
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Deletes a flight based on its ID.
- Steps:
- Uses Mongoose’s
findByIdAndDelete()
method to find and delete a flight by its ID (req.params.id
). - Checks if the flight exists (
flight !== null
), if not, responds with status 404 (Not Found). - Responds with status 200 (OK) and JSON containing a success message indicating the flight was deleted.
- Catches and handles any errors that occur during the delete operation.
- Uses Mongoose’s
In the provided Node.js code snippet for handling flights (Flight
model), pagination and filtering are implemented to manage large datasets efficiently and allow clients to query flights based on specific criteria. Here’s how pagination and filtering are implemented:
Pagination
Pagination allows the API to return a subset of results at a time, which is useful for improving performance and managing large datasets.
Query Parameters Used:
page
: Specifies the current page number. Defaults to1
.limit
: Specifies the number of items per page. Defaults to10
.
Implementation:
- The
getAllFlights
function starts by extracting pagination parameters (page
andlimit
) fromreq.query
. - Calculates the number of items to skip (
skip
) based on the current page and limit. - Uses
.limit(limit)
and.skip((page - 1) * limit)
in the MongoDB query to control pagination. .limit(limit)
ensures that only up tolimit
number of documents are fetched per page..skip((page - 1) * limit)
skips the appropriate number of documents to reach the desired page.
- The
Filtering
Filtering allows clients to narrow down the results based on specified criteria, such as flight number, origin airport, destination airport, and departure date.
Query Parameters Used:
search
: Performs a case-insensitive search across multiple fields (FlightNumber
,OriginAirportCode
,DestinationAirportCode
).origin
: Filters flights by their origin airport code.destination
: Filters flights by their destination airport code.date
: Filters flights by departure date.
Implementation:
- Constructs a
filter
object initially as an empty object{}
. - Checks if
search
is provided:- Uses
$or
operator to perform a regex search across multiple fields (FlightNumber
,OriginAirportCode
,DestinationAirportCode
).
- Uses
- Checks if
origin
anddestination
are provided:- Filters by
OriginAirportCode
andDestinationAirportCode
respectively.
- Filters by
- Checks if
date
is provided:- Converts the
date
string to aDate
object. - Uses
$gte
(greater than or equal to) and$lt
(less than) operators to filter flights based on departure date.
- Converts the
- Constructs a
Example Query
For example, a GET request to /api/flights?page=2&limit=10&origin=JFK&date=2023-10-10
would:
- Fetch flights that depart from JFK on October 10, 2023.
- Return the second page of results, with each page containing up to 10 flights.
Summary
- Pagination ensures that large datasets are split into manageable pages.
- Filtering allows clients to retrieve specific subsets of data based on various criteria.
- Combined, pagination and filtering enable efficient querying and retrieval of flights based on client requirements, enhancing API performance and usability.
This setup provides a robust way to handle large datasets of flights while allowing clients flexibility in querying specific subsets of data based on their needs. Adjustments can be made to query parameters and MongoDB queries as per the application’s requirements and schema.
2. Passenger Controller with Pagination, Filtering, and Search
controllers/passengerController.js
const Passenger = require('../models/Passenger');
// Create a new passenger
exports.createPassenger = async (req, res) => {
try {
const passenger = new Passenger(req.body);
await passenger.save();
res.status(201).json(passenger);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get all passengers with pagination, filtering, and search
exports.getAllPassengers = async (req, res) => {
const { page = 1, limit = 10, search } = req.query;
const filter = {};
if (search) {
filter.$or = [
{ FirstName: { $regex: search, $options: 'i' } },
{ LastName: { $regex: search, $options: 'i' } },
{ Email: { $regex: search, $options: 'i' } },
];
}
try {
const passengers = await Passenger.find(filter)
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
const count = await Passenger.countDocuments(filter);
res.status(200).json({
passengers,
totalPages: Math.ceil(count / limit),
currentPage: page
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single passenger by ID
exports.getPassengerById = async (req, res) => {
try {
const passenger = await Passenger.findById(req.params.id);
if (!passenger) {
return res.status(404).json({ message: 'Passenger not found' });
}
res.status(200).json(passenger);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update a passenger by ID
exports.updatePassengerById = async (req, res) => {
try {
const passenger = await Passenger.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!passenger) {
return res.status(404).json({ message: 'Passenger not found' });
}
res.status(200).json(passenger);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete a passenger by ID
exports.deletePassengerById = async (req, res) => {
try {
const passenger = await Passenger.findByIdAndDelete(req.params.id);
if (!passenger) {
return res.status(404).json({ message: 'Passenger not found' });
}
res.status(200).json({ message: 'Passenger deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
The code you’ve provided defines CRUD (Create, Read, Update, Delete) operations for the Passenger
model in a Node.js Express application using Mongoose. Let’s break down each function:
1. Create a New Passenger (createPassenger
)
// Create a new passenger
exports.createPassenger = async (req, res) => {
try {
const passenger = new Passenger(req.body); // Create a new Passenger object using req.body data
await passenger.save(); // Save the passenger to the database
res.status(201).json(passenger); // Respond with the created passenger object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Handles POST requests to create a new passenger.
- Steps:
- Creates a new instance of the
Passenger
model using data fromreq.body
. - Saves the passenger to the MongoDB database using
passenger.save()
. - Responds with status 201 (Created) and the JSON representation of the created passenger.
- Catches and handles any errors that occur during the creation process.
- Creates a new instance of the
2. Get All Passengers with Pagination, Filtering, and Search (getAllPassengers
)
// Get all passengers with pagination, filtering, and search
exports.getAllPassengers = async (req, res) => {
const { page = 1, limit = 10, search } = req.query;
const filter = {};
if (search) {
filter.$or = [
{ FirstName: { $regex: search, $options: 'i' } },
{ LastName: { $regex: search, $options: 'i' } },
{ Email: { $regex: search, $options: 'i' } },
];
}
try {
const passengers = await Passenger.find(filter)
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
const count = await Passenger.countDocuments(filter);
res.status(200).json({
passengers,
totalPages: Math.ceil(count / limit),
currentPage: page
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
Functionality: Retrieves passengers with support for pagination, filtering, and search.
Query Parameters:
page
,limit
: Pagination parameters to control the number of results per page and current page.search
: Searches for passengers based on first name, last name, or email.
Steps:
- Constructs a
filter
object based on provided query parameters to support flexible search criteria. - Uses Mongoose’s
find()
method withlimit
,skip
, andexec()
for pagination. - Counts total documents matching the filter criteria for calculating total pages.
- Responds with status 200 (OK) and JSON containing passengers array, total pages, and current page.
- Catches and handles any errors that occur during the query execution.
- Constructs a
3. Get a Single Passenger by ID (getPassengerById
)
// Get a single passenger by ID
exports.getPassengerById = async (req, res) => {
try {
const passenger = await Passenger.findById(req.params.id); // Find a passenger by its ID
if (!passenger) {
return res.status(404).json({ message: 'Passenger not found' }); // Handle case where passenger is not found
}
res.status(200).json(passenger); // Respond with the found passenger object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Retrieves a single passenger based on its ID.
- Steps:
- Uses Mongoose’s
findById()
method to find a passenger by its MongoDB document ID (req.params.id
). - Checks if the passenger exists (
passenger !== null
), if not, responds with status 404 (Not Found). - Responds with status 200 (OK) and JSON containing the passenger object.
- Catches and handles any errors that occur during the query execution.
- Uses Mongoose’s
4. Update a Passenger by ID (updatePassengerById
)
// Update a passenger by ID
exports.updatePassengerById = async (req, res) => {
try {
const passenger = await Passenger.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!passenger) {
return res.status(404).json({ message: 'Passenger not found' }); // Handle case where passenger is not found
}
res.status(200).json(passenger); // Respond with the updated passenger object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Updates a passenger based on its ID.
- Steps:
- Uses Mongoose’s
findByIdAndUpdate()
method to find and update a passenger by its ID (req.params.id
). req.body
contains the updated passenger data.- Options
{ new: true, runValidators: true }
ensures the updated document is returned and runs validators on update. - Checks if the passenger exists (
passenger !== null
), if not, responds with status 404 (Not Found). - Responds with status 200 (OK) and JSON containing the updated passenger object.
- Catches and handles any errors that occur during the update operation.
- Uses Mongoose’s
5. Delete a Passenger by ID (deletePassengerById
)
// Delete a passenger by ID
exports.deletePassengerById = async (req, res) => {
try {
const passenger = await Passenger.findByIdAndDelete(req.params.id); // Find and delete a passenger by its ID
if (!passenger) {
return res.status(404).json({ message: 'Passenger not found' }); // Handle case where passenger is not found
}
res.status(200).json({ message: 'Passenger deleted successfully' }); // Respond with success message
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Deletes a passenger based on its ID.
- Steps:
- Uses Mongoose’s
findByIdAndDelete()
method to find and delete a passenger by its ID (req.params.id
). - Checks if the passenger exists (
passenger !== null
), if not, responds with status 404 (Not Found). - Responds with status 200 (OK) and JSON containing a success message indicating the passenger was deleted.
- Catches and handles any errors that occur during the delete operation.
- Uses Mongoose’s
These functions provide comprehensive CRUD operations for managing passengers in a Node.js Express application using Mongoose. They handle creation, retrieval (all and by ID), updating, and deletion of passenger records, incorporating features like pagination, filtering, and error handling to ensure robust and efficient operation of the passenger management system. Adjustments can be made based on specific application requirements and business logic.
3. Airline Controller with Pagination, Filtering, and Search
controllers/airlineController.js
const Airline = require('../models/Airline');
// Create a new airline
exports.createAirline = async (req, res) => {
try {
const airline = new Airline(req.body);
await airline.save();
res.status(201).json(airline);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get all airlines with pagination, filtering, and search
exports.getAllAirlines = async (req, res) => {
const { page = 1, limit = 10, search, region } = req.query;
const filter = {};
if (search) {
filter.AirlineName = { $regex: search, $options: 'i' };
}
if (region) filter.OperatingRegion = region;
try {
const airlines = await Airline.find(filter)
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
const count = await Airline.countDocuments(filter);
res.status(200).json({
airlines,
totalPages: Math.ceil(count / limit),
currentPage: page
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single airline by ID
exports.getAirlineById = async (req, res) => {
try {
const airline = await Airline.findById(req.params.id);
if (!airline) {
return res.status(404).json({ message: 'Airline not found' });
}
res.status(200).json(airline);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update an airline by ID
exports.updateAirlineById = async (req, res) => {
try {
const airline = await Airline.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!airline) {
return res.status(404).json({ message: 'Airline not found' });
}
res.status(200).json(airline);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete an airline by ID
exports.deleteAirlineById = async (req, res) => {
try {
const airline = await Airline.findByIdAndDelete(req.params.id);
if (!airline) {
return res.status(404).json({ message: 'Airline not found' });
}
res.status(200).json({ message: 'Airline deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
1. Create a New Airline (createAirline
)
exports.createAirline = async (req, res) => {
try {
const airline = new Airline(req.body); // Create a new Airline instance with request body
await airline.save(); // Save the airline to the database
res.status(201).json(airline); // Respond with the saved airline object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Handles HTTP POST requests to create a new airline.
- Steps:
- Creates a new instance of the
Airline
model using data fromreq.body
. - Saves the newly created airline to the MongoDB database.
- Responds with status 201 (Created) and JSON containing the created airline object.
- Handles errors with status 500 (Internal Server Error) and sends an error message as JSON.
- Creates a new instance of the
2. Get All Airlines with Pagination, Filtering, and Search (getAllAirlines
)
exports.getAllAirlines = async (req, res) => {
const { page = 1, limit = 10, search, region } = req.query;
const filter = {};
if (search) {
filter.AirlineName = { $regex: search, $options: 'i' }; // Case-insensitive regex search by AirlineName
}
if (region) {
filter.OperatingRegion = region; // Filter by OperatingRegion
}
try {
const airlines = await Airline.find(filter)
.limit(limit * 1)
.skip((page - 1) * limit)
.exec(); // Execute query with pagination
const count = await Airline.countDocuments(filter); // Count total documents matching the filter
res.status(200).json({
airlines,
totalPages: Math.ceil(count / limit), // Calculate total pages for pagination
currentPage: page // Current page number
});
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Retrieves airlines from the database with support for pagination, filtering by name (
search
), and by operating region (region
). - Steps:
- Parses query parameters (
page
,limit
,search
,region
) fromreq.query
. - Constructs a
filter
object based on provided search and region parameters. - Uses Mongoose’s
find()
method withlimit
,skip
, andexec()
for pagination and execution. - Counts total documents matching the filter criteria to calculate total pages.
- Responds with status 200 (OK) and JSON containing the retrieved airlines array, total pages, and current page number.
- Handles errors with status 500 (Internal Server Error) and sends an error message as JSON.
- Parses query parameters (
3. Get a Single Airline by ID (getAirlineById
)
exports.getAirlineById = async (req, res) => {
try {
const airline = await Airline.findById(req.params.id); // Find an airline by its ID
if (!airline) {
return res.status(404).json({ message: 'Airline not found' }); // Handle case where airline is not found
}
res.status(200).json(airline); // Respond with the found airline object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Retrieves a single airline based on its MongoDB document ID.
- Steps:
- Uses Mongoose’s
findById()
method to find an airline by its ID (req.params.id
). - Checks if the airline exists (
airline !== null
); if not, responds with status 404 (Not Found). - Responds with status 200 (OK) and JSON containing the found airline object.
- Handles errors with status 500 (Internal Server Error) and sends an error message as JSON.
- Uses Mongoose’s
4. Update an Airline by ID (updateAirlineById
)
exports.updateAirlineById = async (req, res) => {
try {
const airline = await Airline.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!airline) {
return res.status(404).json({ message: 'Airline not found' }); // Handle case where airline is not found
}
res.status(200).json(airline); // Respond with the updated airline object
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Updates an airline based on its MongoDB document ID.
- Steps:
- Uses Mongoose’s
findByIdAndUpdate()
method to find and update an airline by its ID (req.params.id
). req.body
contains the updated airline data.- Options
{ new: true, runValidators: true }
ensures the updated document is returned and runs validators on update. - Checks if the airline exists (
airline !== null
); if not, responds with status 404 (Not Found). - Responds with status 200 (OK) and JSON containing the updated airline object.
- Handles errors with status 500 (Internal Server Error) and sends an error message as JSON.
- Uses Mongoose’s
5. Delete an Airline by ID (deleteAirlineById
)
exports.deleteAirlineById = async (req, res) => {
try {
const airline = await Airline.findByIdAndDelete(req.params.id); // Find and delete an airline by its ID
if (!airline) {
return res.status(404).json({ message: 'Airline not found' }); // Handle case where airline is not found
}
res.status(200).json({ message: 'Airline deleted successfully' }); // Respond with success message
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any errors
}
};
- Functionality: Deletes an airline based on its MongoDB document ID.
- Steps:
- Uses Mongoose’s
findByIdAndDelete()
method to find and delete an airline by its ID (req.params.id
). - Checks if the airline exists (
airline !== null
); if not, responds with status 404 (Not Found). - Responds with status 200 (OK) and JSON containing a success message indicating the airline was deleted.
- Handles errors with status 500 (Internal Server Error) and sends an error message as JSON.
- Uses Mongoose’s
4. Airport Controller with Pagination, Filtering, and Search
controllers/airportController.js
const Airport = require('../models/Airport');
// Create a new airport
exports.createAirport = async (req, res) => {
try {
const airport = new Airport(req.body);
await airport.save();
res.status(201).json(airport);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get all airports with pagination, filtering, and search
exports.getAllAirports = async (req, res) => {
const { page = 1, limit = 10, search, location } = req.query;
const filter = {};
if (search) {
filter.$or = [
{ AirportCode: { $regex: search, $options: 'i' } },
{ AirportName: { $regex: search, $options: 'i' } },
{ Location: { $regex: search, $options: 'i' } },
];
}
if (location) filter.Location = { $regex: location, $options: 'i' };
try {
const airports = await Airport.find(filter)
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
const count = await Airport.countDocuments(filter);
res.status(200).json({
airports,
totalPages: Math.ceil(count / limit),
currentPage: page
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single airport by code
exports.getAirportByCode = async (req, res) => {
try {
const airport = await Airport.findOne({ AirportCode: req.params.code });
if (!airport) {
return res.status(404).json({ message: 'Airport not found' });
}
res.status(200).json(airport);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update an airport by code
exports.updateAirportByCode = async (req, res) => {
try {
const airport = await Airport.findOneAndUpdate({ AirportCode: req.params.code }, req.body, { new: true, runValidators: true });
if (!airport) {
return res.status(404).json({ message: 'Airport not found' });
}
res.status(200).json(airport);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete an airport by code
exports.deleteAirportByCode = async (req, res) => {
try {
const airport = await Airport.findOneAndDelete({ AirportCode: req.params.code });
if (!airport) {
return res.status(404).json({ message: 'Airport not found' });
}
res.status(200).json({ message: 'Airport deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
Create a New Airport (createAirport
)
const Airport = require('../models/Airport');
exports.createAirport = async (req, res) => {
try {
const airport = new Airport(req.body);
await airport.save();
res.status(201).json(airport);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
- Functionality: Creates a new airport document based on the data in
req.body
. - Steps:
- Creates a new
Airport
instance withreq.body
data. - Saves the new airport to the database using
airport.save()
. - Responds with status 201 (Created) and JSON data of the newly created airport.
- Handles errors by responding with status 500 (Internal Server Error) and an error message in JSON format.
- Creates a new
Get All Airports with Pagination, Filtering, and Search (getAllAirports
)
exports.getAllAirports = async (req, res) => {
const { page = 1, limit = 10, search, location } = req.query;
const filter = {};
if (search) {
filter.$or = [
{ AirportCode: { $regex: search, $options: 'i' } },
{ AirportName: { $regex: search, $options: 'i' } },
{ Location: { $regex: search, $options: 'i' } },
];
}
if (location) filter.Location = { $regex: location, $options: 'i' };
try {
const airports = await Airport.find(filter)
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
const count = await Airport.countDocuments(filter);
res.status(200).json({
airports,
totalPages: Math.ceil(count / limit),
currentPage: page
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
- Functionality: Retrieves all airports with optional pagination, filtering by search terms (
search
) and location (location
). - Steps:
- Parses
page
,limit
,search
, andlocation
fromreq.query
. - Constructs
filter
object based on provided search and location parameters. - Uses
Airport.find()
withfilter
to retrieve airports matching criteria. - Limits and skips results based on pagination parameters.
- Calculates total pages based on the count of filtered airports.
- Responds with status 200 (OK) and JSON containing airports array, total pages, and current page.
- Handles errors by responding with status 500 (Internal Server Error) and an error message in JSON format.
- Parses
Get a Single Airport by Code (getAirportByCode
)
exports.getAirportByCode = async (req, res) => {
try {
const airport = await Airport.findOne({ AirportCode: req.params.code });
if (!airport) {
return res.status(404).json({ message: 'Airport not found' });
}
res.status(200).json(airport);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
- Functionality: Retrieves a single airport by its unique
AirportCode
. - Steps:
- Uses
Airport.findOne()
to find an airport withAirportCode
matchingreq.params.code
. - If no airport is found, responds with status 404 (Not Found) and an error message.
- Responds with status 200 (OK) and JSON data of the found airport.
- Handles errors by responding with status 500 (Internal Server Error) and an error message in JSON format.
- Uses
Update an Airport by Code (updateAirportByCode
)
exports.updateAirportByCode = async (req, res) => {
try {
const airport = await Airport.findOneAndUpdate(
{ AirportCode: req.params.code },
req.body,
{ new: true, runValidators: true }
);
if (!airport) {
return res.status(404).json({ message: 'Airport not found' });
}
res.status(200).json(airport);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
- Functionality: Updates an airport identified by its
AirportCode
. - Steps:
- Uses
Airport.findOneAndUpdate()
to find and update an airport withAirportCode
matchingreq.params.code
. req.body
contains the updated airport data.- Options
{ new: true, runValidators: true }
ensures the updated document is returned and runs validators on update. - Checks if the airport exists (
airport !== null
); if not, responds with status 404 (Not Found). - Responds with status 200 (OK) and JSON data of the updated airport.
- Handles errors by responding with status 500 (Internal Server Error) and an error message in JSON format.
- Uses
Delete an Airport by Code (deleteAirportByCode
)
exports.deleteAirportByCode = async (req, res) => {
try {
const airport = await Airport.findOneAndDelete({ AirportCode: req.params.code });
if (!airport) {
return res.status(404).json({ message: 'Airport not found' });
}
res.status(200).json({ message: 'Airport deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
- Functionality: Deletes an airport identified by its
AirportCode
. - Steps:
- Uses
Airport.findOneAndDelete()
to find and delete an airport withAirportCode
matchingreq.params.code
. - Checks if the airport exists (
airport !== null
); if not, responds with status 404 (Not Found). - Responds with status 200 (OK) and a success message indicating the airport was deleted.
- Handles errors by responding with status 500 (Internal Server Error) and an error message in JSON format.
- Uses
These functions provide comprehensive CRUD operations for managing airport data in your application. They include error handling to ensure robustness and respond with appropriate HTTP statuses and messages based on the outcome of each operation. Adjust the filtering and search logic (filter
object construction) as needed to fit your specific application requirements.
EXPLANATION HOW FILTER WORKS
In the context of the getAllAirports
function, the filter
object is used to construct MongoDB query conditions based on the query parameters received from the request (req.query
). Here’s how the filter
object works and how it helps in retrieving airports with pagination, filtering, and search functionality:
Definition of Query Parameters
const { page = 1, limit = 10, search, location } = req.query;
- page: Specifies the current page number of results to fetch (default: 1).
- limit: Specifies the maximum number of results per page (default: 10).
- search: Provides a string to search for matching values across multiple fields.
- location: Filters results based on the location of airports.
Construction of filter
Object
const filter = {};
if (search) {
filter.$or = [
{ AirportCode: { $regex: search, $options: 'i' } },
{ AirportName: { $regex: search, $options: 'i' } },
{ Location: { $regex: search, $options: 'i' } },
];
}
if (location) {
filter.Location = { $regex: location, $options: 'i' };
}
- Explanation:
Search Filtering (
$or
operator):- If
search
parameter exists (search
is truthy), it creates an array of conditions using the$or
operator. - Each condition in the array specifies a field (
AirportCode
,AirportName
,Location
) that should match the search string (search
) using case-insensitive regex ($regex
with$options: 'i'
). - This allows the query to find airports where any of these fields match the search criteria.
- If
Location Filtering:
- If
location
parameter exists (location
is truthy), it sets a direct regex match for theLocation
field. - The
$regex
operator with$options: 'i'
ensures case-insensitive matching.
- If
Usage in MongoDB Query
const airports = await Airport.find(filter)
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
- find(filter): Uses the
filter
object to retrieve airports matching the specified conditions. - limit(limit * 1): Limits the number of results returned per page based on the
limit
parameter. - skip((page – 1) * limit): Skips the appropriate number of documents based on the current page to implement pagination.
Counting Total Documents
const count = await Airport.countDocuments(filter);
- countDocuments(filter): Counts the total number of documents that match the
filter
conditions, providing the total count for pagination calculation.
Response Object
res.status(200).json({
airports,
totalPages: Math.ceil(count / limit),
currentPage: page
});
- airports: Array of airports matching the filter criteria for the current page.
- totalPages: Calculated based on the total count of documents (
count
) divided by thelimit
per page. - currentPage: Indicates the current page number being returned.
Error Handling
} catch (error) {
res.status(500).json({ message: error.message });
}
- Handles any errors that occur during the database query or processing and responds with an appropriate HTTP status code (
500
for Internal Server Error) along with an error message.
Summary
The filter
object dynamically constructs MongoDB query conditions based on the presence of search
and location
parameters in the request. It allows flexible searching across multiple fields (AirportCode
, AirportName
, Location
) and straightforward filtering by location. This approach ensures that the getAllAirports
function can handle diverse querying requirements efficiently while supporting pagination for large datasets. Adjustments can be made to the fields and operators used in the filter
object to suit specific application needs or additional filtering criteria.
5. Booking Controller with Pagination, Filtering, and Search
controllers/bookingController.js
const Booking = require('../models/Booking');
// Create a new booking
exports.createBooking = async (req, res) => {
try {
const booking = new Booking(req.body);
await booking.save();
res.status(201).json(booking);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get all bookings with pagination, filtering, and search
exports.getAllBookings = async (req, res) => {
const { page = 1, limit = 10, passenger, flight, status } = req.query;
const filter = {};
if (passenger) filter.PassengerID = passenger;
if (flight) filter.FlightID = flight;
if (status) filter.PaymentStatus = status;
try {
const bookings = await Booking.find(filter)
.populate('FlightID PassengerID')
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
const count = await Booking.countDocuments(filter);
res.status(200).json({
bookings,
totalPages: Math.ceil(count / limit),
currentPage: page
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single booking by ID
exports.getBookingById = async (req, res) => {
try {
const booking = await Booking.findById(req.params.id).populate('FlightID PassengerID');
if (!booking) {
return res.status(404).json({ message: 'Booking not found' });
}
res.status(200).json(booking);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update a booking by ID
exports.updateBookingById = async (req, res) => {
try {
const booking = await Booking.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!booking) {
return res.status(404).json({ message: 'Booking not found' });
}
res.status(200).json(booking);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete a booking by ID
exports.deleteBookingById = async (req, res) => {
try {
const booking = await Booking.findByIdAndDelete(req.params.id);
if (!booking) {
return res.status(404).json({ message: 'Booking not found' });
}
res.status(200).json({ message: 'Booking deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
createBooking
This function creates a new booking by receiving data from req.body
, which is expected to contain details of the booking. It attempts to save the booking to the database and responds with the newly created booking object if successful, or an error message if an error occurs.
- Purpose: To create a new booking record in the database.
- Method:
POST
- Endpoint:
/api/bookings
- Usage: Send a POST request to
/api/bookings
with the booking details in the request body (req.body
).
getAllBookings
This function retrieves all bookings from the database with support for pagination, filtering, and search based on query parameters (req.query
). It populates the FlightID
and PassengerID
fields using references to the Flight
and Passenger
models.
- Purpose: To fetch a list of bookings with optional filtering and pagination.
- Method:
GET
- Endpoint:
/api/bookings
- Query Parameters:
page
: Specifies the current page number (default: 1).limit
: Specifies the number of results per page (default: 10).passenger
: Filters bookings byPassengerID
.flight
: Filters bookings byFlightID
.status
: Filters bookings byPaymentStatus
.
- Usage: Send a GET request to
/api/bookings
with optional query parameters to filter and paginate results.
getBookingById
This function retrieves a single booking by its unique identifier (req.params.id
). It uses findById
to fetch the booking from the database and populates the FlightID
and PassengerID
fields for detailed information.
- Purpose: To fetch a specific booking by its ID.
- Method:
GET
- Endpoint:
/api/bookings/:id
- Parameters:
id
– The unique identifier of the booking. - Usage: Send a GET request to
/api/bookings/:id
to retrieve details of a specific booking.
updateBookingById
This function updates an existing booking identified by req.params.id
with new data provided in req.body
. It uses findByIdAndUpdate
to find and update the booking in the database, ensuring the updated booking object is returned ({ new: true }
) and runs validators ({ runValidators: true }
).
- Purpose: To update details of a specific booking.
- Method:
PUT
orPATCH
- Endpoint:
/api/bookings/:id
- Parameters:
id
– The unique identifier of the booking to update. - Usage: Send a PUT or PATCH request to
/api/bookings/:id
with updated booking details in the request body (req.body
).
deleteBookingById
This function deletes a booking from the database based on its unique identifier (req.params.id
). It uses findByIdAndDelete
to find and remove the booking document.
- Purpose: To delete a specific booking from the database.
- Method:
DELETE
- Endpoint:
/api/bookings/:id
- Parameters:
id
– The unique identifier of the booking to delete. - Usage: Send a DELETE request to
/api/bookings/:id
to remove a booking from the database.
Summary
These functions collectively provide a RESTful API interface for managing bookings in a Node.js and Express application. They utilize Mongoose methods for interacting with MongoDB, handle various HTTP methods (GET
, POST
, PUT
, PATCH
, DELETE
), support pagination, filtering, and populate related data (FlightID
and PassengerID
) for detailed information retrieval. Error handling ensures that appropriate status codes (500
for internal server errors, 404
for not found) are returned in case of errors during database operations. Adjustments can be made based on specific application requirements, such as adding additional validation or refining search criteria.
6. Payment Controller with Pagination, Filtering, and Search
controllers/paymentController.js
const Payment = require('../models/Payment');
// Create a new payment
exports.createPayment = async (req, res) => {
try {
const payment = new Payment(req.body);
await payment.save();
res.status(201).json(payment);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get all payments with pagination, filtering, and search
exports.getAllPayments = async (req, res) => {
const { page = 1, limit = 10, method, amountMin, amountMax } = req.query;
const filter = {};
if (method) filter.PaymentMethod = method;
if (amountMin) filter.Amount = { $gte: amountMin };
if (amountMax) {
filter.Amount = filter.Amount || {};
filter.Amount.$lte = amountMax;
}
try {
const payments = await Payment.find(filter)
.populate('BookingID')
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
const count = await Payment.countDocuments(filter);
res.status(200).json({
payments,
totalPages: Math.ceil(count / limit),
currentPage: page
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single payment by ID
exports.getPaymentById = async (req, res) => {
try {
const payment = await Payment.findById(req.params.id).populate('BookingID');
if (!payment) {
return res.status(404).json({ message: 'Payment not found' });
}
res.status(200).json(payment);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update a payment by ID
exports.updatePaymentById = async (req, res) => {
try {
const payment = await Payment.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!payment) {
return res.status(404).json({ message: 'Payment not found' });
}
res.status(200).json(payment);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete a payment by ID
exports.deletePaymentById = async (req, res) => {
try {
const payment = await Payment.findByIdAndDelete(req.params.id);
if (!payment) {
return res.status(404).json({ message: 'Payment not found' });
}
res.status(200).json({ message: 'Payment deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
In the provided code, there are functions implemented to handle CRUD operations (Create, Read, Update, Delete) for payments using the Payment
model in a Node.js and Express application. Let’s break down how each function works:
createPayment
This function creates a new payment record in the database based on the data received in req.body
. It attempts to save the payment object using Payment(req.body).save()
.
- Purpose: Creates a new payment.
- HTTP Method:
POST
- Endpoint:
/api/payments
- Request Body: Contains the details of the payment.
- Response: If successful, returns HTTP status 201 (Created) with the newly created payment object. If there’s an error, responds with HTTP status 500 (Internal Server Error) and an error message.
getAllPayments
This function retrieves all payments from the database with support for pagination, filtering, and search based on query parameters (req.query
).
- Purpose: Retrieves a list of payments.
- HTTP Method:
GET
- Endpoint:
/api/payments
- Query Parameters:
page
: Specifies the current page number (default: 1).limit
: Specifies the number of results per page (default: 10).method
: Filters payments by payment method.amountMin
andamountMax
: Filters payments by amount range.
- Response: Returns an array of payments, along with pagination metadata (
totalPages
,currentPage
). If there’s an error, responds with HTTP status 500 and an error message.
getPaymentById
This function retrieves a single payment by its unique identifier (req.params.id
).
- Purpose: Retrieves a specific payment by ID.
- HTTP Method:
GET
- Endpoint:
/api/payments/:id
- Parameters:
id
– The unique identifier of the payment. - Response: If the payment is found, returns HTTP status 200 with the payment object. If not found, responds with HTTP status 404 (Not Found) and a relevant error message. If there’s an error during retrieval, responds with HTTP status 500 and an error message.
updatePaymentById
This function updates an existing payment identified by req.params.id
with new data provided in req.body
.
- Purpose: Updates details of a specific payment.
- HTTP Method:
PUT
orPATCH
- Endpoint:
/api/payments/:id
- Parameters:
id
– The unique identifier of the payment to update. - Request Body: Contains the updated details of the payment.
- Response: If the payment is successfully updated, returns HTTP status 200 with the updated payment object. If the payment is not found, responds with HTTP status 404 and a relevant error message. If there’s a validation error, responds with HTTP status 500 and an error message.
deletePaymentById
This function deletes a payment from the database based on its unique identifier (req.params.id
).
- Purpose: Deletes a specific payment.
- HTTP Method:
DELETE
- Endpoint:
/api/payments/:id
- Parameters:
id
– The unique identifier of the payment to delete. - Response: If the payment is successfully deleted, returns HTTP status 200 with a success message. If the payment is not found, responds with HTTP status 404 and a relevant error message. If there’s an error during deletion, responds with HTTP status 500 and an error message.
Summary
These functions collectively provide endpoints to manage payments within a RESTful API using Node.js and Express. They utilize Mongoose for interacting with MongoDB and handle various aspects such as creation, retrieval, updating, and deletion of payment records. Error handling ensures appropriate HTTP status codes are returned based on the outcome of each operation. Adjustments can be made based on specific application requirements, such as adding additional validation or refining search criteria.
In this module, we implemented advanced pagination, filtering, and search functionalities within the controllers for managing flights, passengers, airlines, airports, bookings, and payments. These features enhance the usability and efficiency of our Flight Reservation System by providing more flexible and efficient data retrieval capabilities. In the next module, we will focus on testing and deploying the entire system online.
Module 7: Testing and Deploying
Overview
In this module, we will focus on testing and deploying the Flight Reservation System. We will write tests to ensure the functionality of our API and then deploy the system to an online platform.
Step-by-Step Guide
1. Testing
We will use the following tools for testing:
- Mocha: Test framework
- Chai: Assertion library
- Supertest: HTTP assertions
Install the testing dependencies:
npm install mocha chai supertest --save-dev
Create a directory for the tests:
mkdir tests
tests/flight.test.js
const chai = require('chai');
const chaiHttp = require('chai-http');
const server = require('../server');
const Flight = require('../models/Flight');
const should = chai.should();
chai.use(chaiHttp);
describe('Flights', () => {
beforeEach((done) => { // Before each test we empty the database
Flight.remove({}, (err) => {
done();
});
});
describe('/GET flights', () => {
it('it should GET all the flights', (done) => {
chai.request(server)
.get('/api/flights')
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a('object');
res.body.flights.should.be.a('array');
done();
});
});
});
describe('/POST flight', () => {
it('it should POST a new flight', (done) => {
let flight = {
FlightNumber: "12345",
DepartureDateTime: "2023-10-10T10:00:00Z",
ArrivalDateTime: "2023-10-10T14:00:00Z",
OriginAirportCode: "JFK",
DestinationAirportCode: "LAX",
AvailableSeats: 100,
AirlineID: "60e5f4c8f75b444b4e5fc4c7"
};
chai.request(server)
.post('/api/flights')
.send(flight)
.end((err, res) => {
res.should.have.status(201);
res.body.should.be.a('object');
res.body.should.have.property('FlightNumber');
res.body.should.have.property('DepartureDateTime');
res.body.should.have.property('ArrivalDateTime');
res.body.should.have.property('OriginAirportCode');
res.body.should.have.property('DestinationAirportCode');
res.body.should.have.property('AvailableSeats');
res.body.should.have.property('AirlineID');
done();
});
});
});
});
This is a test suite using Chai and Chai-HTTP to test the endpoints related to flights in a Node.js application. Let’s break down the important aspects of this test suite:
Dependencies
- chai: Assertion library that provides various assertion styles.
- chaiHttp: Plugin for Chai that provides HTTP integration testing capabilities.
- server: This is assumed to be the server instance or app exported from
../server
. - Flight: Mongoose model representing flights.
- should: Chai’s BDD style assertion.
Test Description
Before Each Hook (
beforeEach
):- Purpose: Clears the
Flight
collection in the database before each test. - Mechanism: Uses
Flight.remove({}, callback)
to remove all documents from theFlight
collection.
- Purpose: Clears the
Test Suite for GET
/api/flights
:- Description: Tests the endpoint to retrieve all flights.
- Expectation:
- Sends a GET request to
/api/flights
. - Expects HTTP status 200 (OK).
- Expects response body to be an object.
- Expects
res.body.flights
to be an array.
- Sends a GET request to
Test Suite for POST
/api/flights
:- Description: Tests the endpoint to create a new flight.
- Setup: Constructs a sample flight object (
flight
) to send as part of the POST request. - Expectation:
- Sends a POST request to
/api/flights
withflight
as the request body. - Expects HTTP status 201 (Created).
- Expects response body to be an object.
- Verifies that the returned flight object contains specific properties (
FlightNumber
,DepartureDateTime
,ArrivalDateTime
,OriginAirportCode
,DestinationAirportCode
,AvailableSeats
,AirlineID
).
- Sends a POST request to
Summary
This test suite ensures that the endpoints for handling flights (GET /api/flights
and POST /api/flights
) behave correctly:
- The
beforeEach
hook ensures a clean slate for each test by clearing theFlight
collection. - Assertions using Chai (
should
) are used to validate HTTP responses and ensure expected properties are present in the returned data.
To run these tests, ensure that your server (server.js
or equivalent) is running and accessible, and adjust the endpoint URLs (/api/flights
) and payload (flight
object) as per your application’s actual implementation.
tests/user.test.js
const chai = require('chai');
const chaiHttp = require('chai-http');
const server = require('../server');
const User = require('../models/User');
const should = chai.should();
chai.use(chaiHttp);
describe('Users', () => {
beforeEach((done) => { // Before each test we empty the database
User.remove({}, (err) => {
done();
});
});
describe('/POST register', () => {
it('it should register a new user', (done) => {
let user = {
username: "testuser",
email: "testuser@example.com",
password: "password123"
};
chai.request(server)
.post('/api/users/register')
.send(user)
.end((err, res) => {
res.should.have.status(201);
res.body.should.be.a('object');
res.body.should.have.property('message').eql('User registered successfully');
done();
});
});
});
describe('/POST login', () => {
it('it should login a user', (done) => {
let user = new User({
username: "testuser",
email: "testuser@example.com",
password: "password123"
});
user.save((err, user) => {
chai.request(server)
.post('/api/users/login')
.send({ email: user.email, password: "password123" })
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a('object');
res.body.should.have.property('message').eql('Logged in successfully');
done();
});
});
});
});
});
This test suite uses Chai and Chai-HTTP to test user registration and login endpoints in a Node.js application. Here’s an explanation of each part of the test suite:
Dependencies
- chai: Assertion library that provides various assertion styles.
- chaiHttp: Plugin for Chai that provides HTTP integration testing capabilities.
- server: This is assumed to be the server instance or app exported from
../server
. - User: Mongoose model representing users.
- should: Chai’s BDD style assertion.
Test Description
Before Each Hook (
beforeEach
):- Purpose: Clears the
User
collection in the database before each test. - Mechanism: Uses
User.remove({}, callback)
to remove all documents from theUser
collection.
- Purpose: Clears the
Test Suite for POST
/api/users/register
:- Description: Tests the endpoint to register a new user.
- Setup: Constructs a sample
user
object (username
,email
,password
) to send as part of the POST request. - Expectation:
- Sends a POST request to
/api/users/register
withuser
as the request body. - Expects HTTP status 201 (Created).
- Expects response body to be an object.
- Verifies that the returned object has a
message
property equal to'User registered successfully'
.
- Sends a POST request to
Test Suite for POST
/api/users/login
:- Description: Tests the endpoint to log in a user.
- Setup: Saves a
user
object to the database usinguser.save()
before sending the login request. - Expectation:
- Sends a POST request to
/api/users/login
withemail
andpassword
. - Expects HTTP status 200 (OK).
- Expects response body to be an object.
- Verifies that the returned object has a
message
property equal to'Logged in successfully'
.
- Sends a POST request to
- Clean Database State: The
beforeEach
hook ensures a clean database state by removing allUser
documents before each test. - Assertions: Chai assertions (
res.should
) are used to validate HTTP responses and ensure expected messages are returned for user registration and login. - Ensure that
server
correctly points to your server implementation (../server
). - Adjust endpoint URLs (
/api/users/register
and/api/users/login
) based on your application’s actual routes. - Adjust the
user
object structure (username
,email
,password
) as per your application’s user registration and login requirements.
This setup provides a basic foundation for testing user registration and login functionality using Chai and Chai-HTTP in a Node.js environment.
Add a test script to your package.json
:
"scripts": {
"test": "mocha tests/**/*.test.js"
}
Run the tests:
npm test
2. Deployment
For deployment, we will use Heroku. Follow these steps to deploy your application.
Install Heroku CLI Download and install the Heroku CLI from Heroku CLI.
Login to Heroku
heroku login
Create a New Heroku Application
heroku create flight-reservation-system
Add MongoDB Add-on
heroku addons:create mongolab
Set Environment Variables
heroku config:set JWT_SECRET=your_jwt_secret
Deploy to Heroku Initialize a git repository if you haven’t already and commit your code.
git init
git add .
git commit -m "Initial commit"
Add Heroku remote and push your code.
heroku git:remote -a flight-reservation-system
git push heroku master
Scale the Application
heroku ps:scale web=1
Open the Application
heroku open
In this module, we have written tests for our Flight Reservation System to ensure its functionality. We then deployed the application to Heroku, a cloud platform as a service (PaaS). By following these steps, you can ensure that your system is both reliable and accessible online.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
Welcome to DevTechTutor.com, your ultimate resource for mastering web development and technology! Whether you're a beginner eager to dive into coding or an experienced developer looking to sharpen your skills, DevTechTutor.com is here to guide you every step of the way. Our mission is to make learning web development accessible, engaging, and effective.