Developing a fullstack E- commerce Application with Typescript. Part 1
Tags: typescript, react, node
Published: 2023-10-14 14:45:37

E-commerce application is like an online shopping mall on the internet. 
It lets you browse, select, and buy products online. You can shop from the comfort of your home, add items to a virtual cart, and complete your purchase with a click of a button. You can also make payment for a product using your debit or credit card.

In this article, we'll be building a robust e-commerce application. 

We'll be working on both the frontend and backend, combining React and Node.js to create a seamless shopping experience.

In this section we will be developing the backend of the e-commerce application. 

In part two of this article, we will be developing the `frontend` of our e-commerce application.

Now we will start by creating the backend of our e-commerce project. Make sure you have `nodejs` installed on your system.

Create a folder. Name it  `backend`.  

Next, we'll initialize our backend application by first setting-up the project `package.json` and add the necessary dependencies.

### Setting Up Node.js package.json:
Then run the following command:
 `npm init -y`
 to initialize a new project. Using the `-y` flag when creating a `package.json` will approve all the defaults.

Now let's add Add `TypeScript` as a `dev` dependency by running this command:

`npm install typescript --save-dev`

Now let's add type definitions to our `Node.js` application through its `@types/node `package. 

This will enable us to write `TypeScript` code that interacts with our Node.js APIs and modules in a type-safe manner.

run this command:
`npm install --save-dev @types/node`

### Create a tsconfig.json file:
`tsconfig.json` files serves as configuration file for `TypeScript` compiler settings. 

Inside this file,  we will specify a variety of options to customize the behavior of the Typescript compiler according to our needs.
Run the command:
`npx tsc --init`

This will add a  `tsconfig.json`  to the root of your backend folder. 
Remove the default configuration and the following code:
{% gist https://gist.github.com/jamesoyanna/a3b34d9f0c59df9c27f78830e96ba285 file=tsconfig.json%}

Here we setup a configuration file for typescript. We specify the ECMAScript target version to be es5.

Now let's update our `package.json` file and also add dependencies we will need for this project.

Change the index.js file in the main section to this -`"main": "server.ts"`,
 Under the script section, add `"start": "nodemon server.ts"`,

Now let's add some of the dependencies we will be using for this project by running the following command:
```

 npm i @types/bcryptjs @types/express @types/jsonwebtoken @types/mongoose @types/morgan @types/multer @types/supertest bcryptjs colors.ts cors express express-async-handler jsonwebtoken mongoose morgan multer @types/cors nodemon supertest @types/node dotenv ts-node
```

You install each package separately if you want.

Your `package.json` file should now look like this. 
{% gist https://gist.github.com/jamesoyanna/729fe67b353f84be87b222e766dd3029 file=package.json%}

### Setting up the file structure:
Now lets setup our file structure for our backend API. 

This will define how different components of our application will interact. 
This is to ensure the organization and maintainability of our project.

```
backend
|-- config/
|   |-- db.ts
|-- controllers/
|   |-- orderController.ts
|   |-- userController.ts
|   |-- paystackController.ts
|   |-- productController.ts
|-- data/
|   |-- models/
|   |   |-- orders.ts
|   |   |-- products.ts
|   |   |-- users.ts
|   |   |-- reviews.ts
|   |-- db.ts
|-- middleware/
|   |-- authMiddleware.ts
|   |-- errorMiddleware.ts
|-- models/
|   |-- index.ts
|   |-- orderModel.ts
|   |-- productModel.ts
|   |-- userModel.ts
|-- routes/
|   |-- orderRoutes.ts
|   |-- paystackRoutes.ts
|   |-- productRoutes.ts
|   |-- uploadRoutes.ts
|   |-- userRoutes.ts
|-- types/
|   |-- express.ts
|   |-- index.ts
|   |-- order.ts
|   |-- product.ts
|   |-- user.ts
|-- uploads/
|   |-- image.png
|-- utils/
|   |-- generateToken.ts
|-- .env
|-- server.ts
|-- package.json
|-- seeder.ts
|-- tsconfig.json
|-- README.md

```
Let's take a look at what the folders represents.
**config:** Contains configuration files including database configuration (database.ts).

**controllers:** Holds controller files responsible for handling different parts of our application routes.

**models:** Contains Mongoose models (e.g., User.ts, Product.ts) that define the structure and shape of our database documents.

**middleware:** It houses the `middleware` functions that we will use in our application routes, such as authentication and validation and error `middleware`s.

**routes:** It defines the route files (e.g userRoutes.ts, productRoutes.ts) that specify the API endpoints and route handlers for various parts of our application.

**types:** It contains the TypeScript type definitions (e.g., user.ts, product.d.ts) for data structures used in our API.

**uploads:** We will use this folder to store temporary uploaded images.

**utils:** We will use it to store utility functions or helper modules that we will use throughout our API.

**.env:** This file stores environment variables including sensitive configuration data and settings.

**server.ts:** This is the main entry point for our application. We will use it to set-up the Express server, load environment variables, configure routes and `middleware`, and listen for incoming requests.

**package.json**: Contains metadata about our API and its dependencies.

**tsconfig.json:** The TypeScript configuration file that specifies how TypeScript should compile our code.

### Setting-up a simple server:
Firstly, let us add a `.env` file. We will use it to store configuration and sensitive information like database credentials that our application will need.

In your root directory, create a file. Name it `.env` and add the following code:

`PORT = 5000`
Here, we added a port number that our application will listen on.

Now let's create a simple server. 
Inside your `backend` directory, create a file. Name it `server.ts` and add the following code:
{% gist https://gist.github.com/jamesoyanna/224400c8deb4089ef443e84b7f71b882 file=server.ts%}
Here in this code above, we are setting up a basic `Node.js` web server using the `Express.js` framework. 

This server will serve as the foundation for building our e-commerce web API.

 We start by importing the necessary packages and modules. These include `express, cors, and morgan`.

We then define a constant `PORT` that represents the port number on which our server will listen. 

We determined this value by checking the `process.env.PORT `environment variable. If `process.env.PORT` is not defined (or falsy), we default to use port 4000.

Next, we created an instance of the Express application by invoking `express()`. Later, we will use this to configure our `routes` and `middleware`.

We use the `cors` middleware by calling `app.use(cors())`. 
This `middleware` enable Cross-Origin Resource Sharing (CORS).

It will allow the server to respond to requests from different origins (domains).

Next, we then configure the server to parse incoming JSON data from request bodies using `app.use(express.json())`. 
This `middleware` parses JSON data and make it accessible in the req.body object.

Next, we set up request logging using the `morgan` middleware. It logs` HTTP` requests and responses to the console.

We defined a single route using `app.get("/", ...)` that handles GET requests to the root path ("/"). 
When a` GET` request is made to this path, the server responds with the message "API IS RUNNING...".

We started our application by calling `app.listen(PORT, ...)`. 
It listens on the `PORT` we defined earlier.

### Connecting our API to a MongoDB Database:
In this section, we will be establishing a connection to a database.

To ensure that you have a MongoDB database instance set up and running, you'll need the connection `URI` for your MongoDB database.

 You can either use a local MongoDB server or a cloud-based service like MongoDB Atlas. 

You can follow the steps in this article to install and use it locally on your system.-[ How to get started with MongoDB](https://www.freecodecamp.org/news/learn-mongodb-a4ce205e7739/)

We have already installed the Mongoose package in our project during setup. 

If you are using a local setup of the MongoDB database on your project, create a database using the MongoDB compass copy your connection string and add it to your `.env` file like this:
`
MONGO_URI = mongodb://localhost:27017/commerce-app`

Inside the root directory(i.e backend folder), create a folder. Name it `config`. 
Inside it, create a file. name it `db.ts` and add the following code:
   {% gist https://gist.github.com/jamesoyanna/21c3aa179904e70539097b84bc973179 file=db.ts%}
Here in this code, we have a module responsible for connecting to a MongoDB database using the Mongoose library, which is a popular library for working with MongoDB in Node.js.

 We imported the `mongoose` library, which allows us to interact with MongoDB.

We then defined an `async` function named `connectDB`. 

We are using this function to establish connection to the MongoDB database.

Inside the `connectDB` function, we first check if the `MONGO_URI `environment variable is defined in the `process.env`.

If the `MONGO_URI` environment variable is defined, then we connect to the database using `mongoose.connect`. 

This function returns a promise that resolves to a connection object (conn) when the connection is successful.

We also check if an error occurs during the database connection attempt, we catch the error and log an error message. 

We call the `process.exit(1)` to terminate our API process with an error exit code.

Lastly, we exported the `connectDB` function so it can be available to use in our `server.ts` file.

Now, let's make the database connection by importing the `connectDB` function into our `server.ts` file and then invoke it.

Your `server.ts` file should now look like this:
    {% gist https://gist.github.com/jamesoyanna/f7e2ab20b5a58dc6c5565f82e16835cf file=server.ts%}
In this code, we have added two lines to configure environment variables and to establish a connection to the database.

dotenv.config() : Here,we imported the `dotenv` package and called its config() method. 
`dotenv` is used to load environment variables from the `.env` file into `process.env`.

**connectDB()** : Here, we invoke the `connectDB` function, which we imported from `"./config/db"`.

This initializes the connection to the MongoDB database. The `connectDB` function is responsible for establising connecting to the database using the MongoDB URI we specified in our environment variables in this case, `MONGO_URI`.

### Adding Types:
TypeScript types and interfaces are defined and used to enhance the type safety and readability of our `Express.js` application. 
Let's add types for the express.
Create a file inside the `types` folder. Name it `express.ts` and add the following code:
{% gist https://gist.github.com/jamesoyanna/3899c7a040926be8f8408ad9a9f2051c file=express.ts%}
 Here in this code, we we start by importing specific types (Request, Response, and NextFunction) from the "express" package.

These types are used to describe the request, response, and next function that are commonly used in Express web applications. 

We use aliasing (as) to rename these types to Req, Res, and Next, respectively, for convenience.

Next, we defined a custom TypeScript interface called `User`. 
We use this interface to describe the structure of user-related data that can be attached to the Express request and response objects. 

It includes properties like _id (a string representing a unique identifier for the user), name (the user's name), email (the user's email address), and an optional property `isAdmin` (a boolean indicating whether the user has administrative privileges).

Finally, we create TypeScript type aliases (Request, Response, and NextFunction) that combine the imported Express types (Req, Res, Next) with the custom User interface.

The Request and Response type alias combines the Req & Res type (representing an Express request and response object) with the User interface. 

This means that when you use `Request` or `Response` in your code, you'll have access to properties defined in the User interface in addition to the standard request or response properties.

Now let's add the User types. Still inside the types folder, create a file. Name it `user.ts ` and add the following code:
{% gist https://gist.github.com/jamesoyanna/eaedc89fbdb4be6e098c05b88b5024bd file=user.ts%}
Here in this code, we defined the TypeScript interfaces and types that we will use together with the Mongoose library to model and interact with user data in a MongoDB database:

We start by defining an interface called `User`. This describes the structure of a user document in our MongoDB database.
 
It includes the name, email, password, and an optional `isAdmin `flag. 

Next, we created an interface called `UserDocument`, which extends the `User` interface and Document type provided by Mongoose. 

This interface represents a document in our MongoDB collection that corresponds to a user. 
It inherits the properties from User and includes an additional method `matchPassword`. 

We will use this method to compare a provided password with the stored hashed password and return a Promise of type boolean.

Lastly, we define an interface called `UserModel`, which extends the `Model` type from Mongoose and specifies that it works with documents of type `UserDocument`. 

We will add an `index.ts` for exporting our types. Create a file in types folder. Name it `index.ts` and add the following code:

`export * from "./user";
export * from "./express";`
The `index.ts` file is using TypeScript's module exports to re-export modules and types from other files.

Later, we will be adding other type definitions for our orders, products, etc.  For now, let us implement the user model.

### Implementing the User model:
Inside the models folder, create a file, name it `userModel.ts` and add the following code to it:
{% gist https://gist.github.com/jamesoyanna/a7a47f30e3caec0cae0ff9ecf6a82462 file=userModel.ts%}
Here in this code, we are defined and configured a Mongoose model for a user in our API that interacts with our database.

We start by importing necessary dependencies. `bcrypt` is used for hashing and comparing passwords securely. 

`model` and `Schema` are from Mongoose, a MongoDB Object Data Modeling (ODM). 
We also imported `UserDocument` from the `types` module.

Then, we created a Mongoose schema (userSchema). This defines the structure of user documents in the MongoDB collection.

It includes fields for name, email, password, and isAdmin, each with their respective data types and validation rules.

We set the timestamps option to true, which automatically creates a `createdAt` timestamp in each user document.

We defined an instance method called `matchPassword`. 
This method is available on user documents we created with this schema and we are using it to compare an entered password with the stored hashed password. 
It uses bcrypt.compare() for secure password comparison.

We then defined a pre-save middleware. It runs before the user model is saved to the database.

The `middleware` checks if the user's password has been modified. If not, it calls the next function to proceed.

If the password has been modified, it generates a salt and hashes the password using `bcrypt` before saving it to the database. 

Finally, we created and exported the `User` model. 
We will use this model to interact with the `"User"` collection in the MongoDB database.

To make all our models available across our application, create a file inside the models folder, name it `index.ts` and add the following code:

```
import { User } from "./userModel";
export { User };
```
Here in this code, we are importing the User model and then re-exporting it from the current module. This means that any other module that imports from this module will have access to the User model as if it were defined in the current module.

### Implementing the Middlewares:
Middlewares are functions or pieces of code that sit in between the HTTP request and the final response, allowing us to perform various tasks and operations on the request and response objects.
We will be using it to authenticate, authorize, login, validate input and handle errors in our API.

We will be adding the auth middleware. Inside your middleware folder, create a file, name it `authMiddleware.ts` and add the following code:
{% gist https://gist.github.com/jamesoyanna/e23ef1f11ce95a45e50042c137f659fc file=authMiddleware.ts%}
Here in this code,we have two middleware functions, `protect` and `admin`, used to protect our routes and control access in our API

We use the `protect middleware`to protect routes from unauthorized users. It's used for authentication and authorization purposes.

 It checks for the presence of a JSON Web Token (JWT) in the request's "Authorization" header. If a valid token is found, it decodes the token to retrieve user information.

The user's information is then attached to the req object for future use. The properties attached to req.user include _id, name, email, and isAdmin.
If the user is not found in the database, we return a 401 Unauthorized response and throws an error.

If the token is invalid, we also return a 401 Unauthorized response and throws an error.
If no token is found, we return a 401 Unauthorized response and throws an error.

 We use the admin `middleware` to protect routes from users who are not flagged as administrators (admins).

Firstly,we check if the user object is present on the req object, which is typically attached by the protect middleware, and whether the isAdmin property is set to true.

If these conditions are met, it allows the request to continue processing by calling next().
If the conditions are not met, we return a 401 Unauthorized response and throws an error, indicating that the user is not authorized as an admin.

Now, let's add the `errorMiddleware`. Inside the `middleware` folder,create a file, name it `errorMiddleware.ts` and add the following code:
{% gist https://gist.github.com/jamesoyanna/6244b6f11cc01765ad10578a1b9fd91d file=errorMiddleware.ts%}
In this code, we have two `middleware` functions, `notFound` and `errorHandler` to handle HTTP errors and error responses. 

We use the `notFound` middleware to handle situations where a requested route or resource is not found (HTTP 404 - Not Found).

It takes three parameters: req (the request object), res (the response object), and next (the next function in the middleware chain).

If a route is reached, and there is no matching route or resource for the requested URL (req.originalUrl), it generates a new error with a message indicating that the requested resource was not found.
It sets the HTTP response status to 404 (Not Found) using res.status(404).
Finally, it calls next(error) to pass the error to the next middleware in the chain.

We use the `errorHandler` middleware is to handle and format error responses.
It takes four parameters: err (the error object), req (the request object), res (the response object), and next (the next function in the middleware chain).

It first determines the HTTP status code to use for the response. If the response status code is 200 (OK), it sets the status code to 500 (Internal Server Error); otherwise, it uses the existing status code.

It sets the HTTP response status to the determined status code using res.status(statusCode).

It sends a JSON response containing an error message (err.message) and, in development mode (when process.env.NODE_ENV is not "production"), the full error stack (err.stack).

In production mode, the stack trace is omitted from the response for security reasons.


### Adding Utility functions:
 Utility function are used to encapsulate common tasks or functionalities in a reusable and modular manner.
Inside the `utils` folder, create a file, name it `generateToken.ts` and add the following code:
{% gist https://gist.github.com/jamesoyanna/bcb6baa339386518c54a92d82b54adfe file=generateToken.ts%}
Here in this code, we have a utility file that contains a function for generating JSON Web Tokens (JWTs) for user authentication and authorization.

We defined a function called `generateToken`.
This function is responsible for generating a JWT for a user.
It takes a user's ID as a parameter. 
The ID is used as a payload in the JWT to uniquely identify the user.

If the JWT_SECRET environment variable is defined (indicating the presence of a secret key for JWT generation), the function proceeds to create a JWT.

It uses the `jwt.sign` method provided by the `jsonwebtoken` library to create a token.
The token payload includes the user's ID.

The `process.env.JWT_SECRET` is used as the secret key for signing the token.

The token is set to expire after 30 days (the expiresIn: "30d" option). The function returns the generated JWT
 
### Implementing the User Controller:
Now let's implement the user controller. Inside the controller folder, create a file. name it `userController.ts` and add the following code:
{% gist https://gist.github.com/jamesoyanna/d9f8777153abda13ddebd56c02c2f708 file=userController.ts%}
In this code, we have two route handler functions in a user controller. 
These functions handle user-related operations, such as authentication and registration, in our API.

The `authUser` function is responsible for user authentication.
 It handles a POST request to the /api/users/login route.

 We firstly extract the email and password from the request body, submitted by a user attempting to log in.

 We then querie the database to find a user with the specified email using `User.findOne({ email })`. 

If a user is found, we proceed to check if the provided password matches the stored password using the matchPassword method.

If the email and password match, it responds with a JSON object containing the user's information (e.g., user ID, name, email, and whether they are an admin) and a JSON Web Token (JWT) generated by the `generateToken` function. 

We will use this token to authenticate and authorize the user in subsequent requests.

If the email and password do not match, we set the response status to 401 (Unauthorized) and throws an error, indicating that the login attempt is invalid.

The `registerUser` function handles user registration.
It handles a POST request to the /api/users route, in our route.

The function first extracts the name, email, and password from the request body, submitted by a user during registration.

We first checks if a user with the provided email already exists by querying the database with `User.findOne({ email })`. 

If a user with the same email is found, we set the response status to 400 (Bad Request) and throws an error indicating that the user already exists.

If the user does not already exist, we create a new user by calling `User.create` with the provided user data.

If the user creation is successful, we respond with a status of 201 (Created) and a JSON object containing the user's information and a JWT generated by the `generateToken` function.

If user creation fails for any reason, we set the response status to 400 and throw an error indicating invalid user data.

We then exported these functions to be used in our routes.

### Adding the UserRoutes:
Inside the `routes` folder, create a file. Name it `userRoutes.ts` and add the following code:
{% gist https://gist.github.com/jamesoyanna/d7e6d82f5370992cb6d1e3c42a8d8abf file=userRoutes.ts%}
Here in this code, we are setting up the routes related for user authentication and registration in our API.

 We imported the necessary modules, including express for creating a router, route handler functions from the `userController`, and `middleware` functions from the authMiddleware module.

We created an instance of an Express router using express.Router() and assigned it to the router variable.

 We define several routes using the .route method on the router.
` / route`: It's used for both registration and getting a list of all users. A POST request to this route invokes the registerUser function, which handles user registration.

A GET request to this route invokes two `middleware` functions, `protect` and `admin`, followed by an operation that gets a list of all users. 

We combined this to protect the route and ensure that only authenticated users with admin privileges can access it.

` /login route:` This route handles user authentication.
A POST request to this route invokes the `authUser` function, which handles user authentication.

The Middleware functions (protect and admin) are used in the route definitions to add an extra layer of logic before reaching the route handler. They perform tasks like user authentication and authorization.

Lastly, we exported the router from this module, making it available for use in other parts of our application.

Now, let's use our routes in the `server.ts` file

Update the code in the `server.ts` file by importing and adding the routes.
The updated server.ts file should now look like this:
{% gist https://gist.github.com/jamesoyanna/2f31e8e505b3e8910754aabb32695f7a file=server.ts%}

Test your endpoints.

### Adding the product Types:
In your types folder, create a file. Name it product.ts and add the following code:
{% gist https://gist.github.com/jamesoyanna/dd4435f8a86bd149583f127b6ea692b3 file=product.ts%}
Here In this code, we are defining the data models and interfaces for products and product reviews in our API. 

We are simply defining TypeScript interfaces for products and product reviews, which we are using to specify the shape of data objects in our database.

The Product interface represents a product and defines the following properties:
**name:** The name of the product.
**image:** A URL or path to the product's image.
description: A description of the product.
**brand:** The brand or manufacturer of the product.
**category:** The category to which the product belongs.
**price:** The price of the product.
**countInStock:** The number of items available in stock.
**rating:** The rating of the product.
**numReviews:** The number of reviews for the product.


The Review interface represents a product review and includes the following properties:
**user:** The ID or username of the user who wrote the review.
**name:** The name of the reviewer.
**rating:** The rating given in the review.
**comment**: The text comment or review content.


 We defined a `ProductInDatabase` Interface. This interface extends the Product interface and adds two additional properties:
**user:** The ID or username of the user associated with the product.
**reviews:** An array of product reviews. Each review conforms to the Review interface.

We defined and exported `ProductDocument` and `ProductModel `Interfaces
These interfaces are related to MongoDB and we are using them to define the structure of product documents in the database.

The `ProductDocument` extends the ProductInDatabase interface and `Document`. 
It represents a document in the database collection and includes all the properties defined in the ProductInDatabase interface.

This interface also inherits functionality from `Document` for interacting with our database.

The `ProductModel` extends Model<ProductDocument>. It represents the model for interacting with the MongoDB collection. It is responsible for creating, querying, and modifying documents in the database collection.

In the index.ts file in your types folder, add this to it to export the product types. 
`export * from "./product";`

### Implementing the Product model:
Inside your models folder, create a file. Name it `productModel.ts` and add the following code:
{% gist https://gist.github.com/jamesoyanna/341e259fd7ff39c57b325b17d571db0c file=productModel.ts%}
In this code, we define Mongoose schemas and a model for our product. 
This represents a product in our database. 
We are using it to define the structure and behavior of the `"Product"` document in our database.

Firstly, we imported the necessary modules from Mongoose, including model and Schema.
We also imported the `ProductDocument` interface we defined earlier.

We defined a `reviewSchema`. This schema describes the structure of a product review with the following fields:
** user:** A reference to the user who wrote the review. It's a MongoDB ObjectId and is required. The ref option specifies that it references the "User" model.

**name:** The name of the reviewer, a required string.
** rating**: The rating given by the reviewer, a required number.
**comment:** The review comment, a required string.

The timestamps option is set to true, which automatically generates `createdAt` and `updatedAt` fields for each review.

We define a `productSchema`. It represents the structure of our product. It has the following fields:

**user:** A reference to the user who added the product
**  name:** The name of the product, a required string.
**image:** The URL of the product's image, a required string.

**brand:** The brand of the product, a required string.
**category:** The category of the product, a required string.
**description:** A description of the product, a required string.
**reviews:** An array of review documents. Each product can have multiple reviews, and we use the `reviewSchema` to define their structure.
**rating:** The overall rating of the product, a number with a default value of 0.
**numReviews:** The number of reviews for the product, a number with a default value of 0.
**price:** The price of the product, a number with a default value of 0.
**countInStock:** The available quantity of the product, a number with a default value of 0.
We also have the `timestamps` set to true.

Lastly, We created and eported a model named "Product". The model is associated with the `productSchema`, and it's of type `ProductDocument`. 
This model represents the product collection in our database and provides methods for interacting with our data.

Inside the index.ts file in the models folder,update it with this code. 
`import { User } from "./userModel";
import { Product } from "./productModel";
export { User, Product };`

### Adding the Product Controller:
In the controller folder, create a file. Name it `productController.ts` and add the following code to it.

{% gist https://gist.github.com/jamesoyanna/85d3795420352ef85027f45b98d46d87 file=productController.ts%}
In this code, we have a function that fetches a list of products from our database.
  
Firstly, we imported the `asyncHandler middleware` from the `express-async-handler` package. We will use this `middleware` to handle asynchronous operations within our route handler.

We imported the `Product` model from the `models` directory.
We also imported the `Response` and `Request` types from the `types`directory to ensure type safety in our route handler.

We defined a route handler function named `getProducts` to handle HTTP GET requests to the `/api/products` endpoint.
It takes `req `(request) and `res` (response) objects as parameters, representing the incoming request and the response to be sent.

We set up pagination for the product list. We define a `pageSize` of 10 products per page and retrieve the page number from the query parameter `pageNumber`. If `pageNumber` is not provided, it defaults to 1.
Pagination allows clients to request specific pages of product results.

We allow clients to search for products by specifying a search keyword in the query parameter keyword.

If a keyword is provided, we create a keyword object to search for products with a partial case-insensitive match in the "name" field using regular expressions.
If no keyword is provided, the keyword object is an empty object, meaning all products will be returned.

To count our products in the database, We use Mongoose's `countDocuments `method to count the total number of products that match the search criteria (including pagination).
We are using this count to determine the total number of pages.

To fetch all products from the database, we use the Mongoose's find method to retrieve the products. 
We apply the search criteria defined by the keyword object.

We limit the number of products to `pageSize` and skip the appropriate number of products based on the current page.
The result is a list of products for the current page.

In response, we send a JSON response to the client, including
the list of products (products), current page number (page), total number of pages, calculated by dividing the total product count by the page size (pages).

We exported the `getProducts` route handler function so that it can be used in our application.

### Adding Product Routes:
Inside your route folder, create a file. Name it `productRoutes.ts` and add the following code:
{% gist https://gist.github.com/jamesoyanna/ec4a28f86f7b64d4e97f4e01391394ff file=productRoutes.ts%}
Here in this code, we're setting up a route for handling requests to get all products from our database.

We imported the `Express` framework.
 We also imported the `getProducts` function from the `productController` module. This function is responsible for fetching and sending a list of products in response to client requests.

`const router = express.Router();`: We create a new router instance using Express. 
`router.route("/").get(getProducts);`: Here, we define a route for the root URL ("/"). 
When a GET request is made to the root URL, it will trigger the `getProducts` function from the `productController`. This function retrieves and sends a list of products as a response.

Finally, we export the router instance so that it can be used in other parts of our application.

Now let's make use of our product route in the `server.ts` file.
In this file, import the `productRoutes` and use it like this:
`import productRoutes from "./routes/productRoutes";
// Use routes
app.use("/api/products/", productRoutes);`

Your `server.ts` file should now look like this:
{% gist https://gist.github.com/jamesoyanna/a4bba3de67b7555a9cd01352b09f8dbb file=server.ts%}

### Implementing orderTypes:
Here we will be adding the types for the order. Inside your `types` folder, create a file. Name it `order.ts`and add the following code:
{% gist https://gist.github.com/jamesoyanna/47e20f954587ab5bc3ff74973a5770e4 file=order.ts%}
Here in this code, we imported the Model and Document types from the Mongoose library. 

We are using these types work with MongoDB models and documents.
We also imported the `ProductDocument` type. It represents the document structure of our product in the database.
We defined some interfaces which represents different aspects of an order.

**OrderItems**: It describes the items within an order, including the name, quantity, image, price, and a reference to the associated product.

**ShippingAddress**: It represents the shipping address for a user, including fields for the address, city, postal code, and country.

**PaymentResult**: It defines the result of a payment transaction, including an ID, status, update time, and email address.

Order: It represents the overall order, combining information about the user who placed the order, the order items, shipping address, payment method, payment result, pricing details (items price, tax price, shipping price, total price), and flags to indicate whether the order is paid and delivered, along with timestamps.

`export interface OrderDocument extends Order, Document {}:` Here, we defined and interface called `OrderDocument`. We are using this interface to extend the `Order` interface and the` Document` type from Mongoose and we also exported it.

`export interface OrderModel extends Model<OrderDocument> {}`: Here, we also define an interface `OrderModel`. This interface extends the Model type from Mongoose and is used to represent a model that interacts with the MongoDB database.

### Implementing order model:
In the models folder, create a file. Name it `orderModel.ts` and add the following code to it:
{% gist https://gist.github.com/jamesoyanna/098dd5ef44f86eeb5c1bd8c458657625 file=orderModel.ts%}
Here in this code, we are defined the schema for an `Order` document in the database. 
It represents an order made by a user in an our application.

Firstly, we imported the necessary modules:
 We imported the `model` and `Schema` classes from the `mongoose` library.

We also imported the `OrderDocument` type.
We then defined the an order schema called `orderSchema` using the Schema class. 
The schema specifies the structure of the `Order` document.
The key fields we added to the schema are as follows:

**user**: It's a reference to the user who placed the order. It's of type ObjectId and is required. The ref property indicates that it references the "User" model.

**orderItems**: An array of objects, each representing an item in the order. Each item has fields like name, quantity (qty), image, price, and references the "Product" model for the product. These items are required.

**shippingAddress**: An object containing address details, including address, city, postal code, and country. All these fields are required.

**paymentMethod**: The payment method used for the order, which is a string and is required.

**paymentResult**: An object containing payment-related information, including an ID, status, update time, and email address. These fields are all optional.

 **itemsPrice, taxPrice, shippingPrice, and totalPrice**: These fields represent the pricing details of the order and are all numbers with default values.

**isPaid and isDelivered**: These are Boolean fields representing whether the order has been paid and delivered, respectively. They are required with default values.

**paidAt and deliveredAt: **These fields store timestamps indicating when the order was paid and delivered, respectively. They are of type Date.

### Implementing orderController:
Inside the `controller` create a folder. name it `orderController.ts` and add the following code:
{% gist https://gist.github.com/jamesoyanna/36d1dbf7dcceab8657d6055796f2cf8f file=orderController.ts%}

Here in this code, we're implementing several functionalities related for managing our orders:

We imported `asyncHandler` from the "express-async-handler" package, which helps handle errors within asynchronous Express routes.

We imported the `Order` model from the "../models/" directory, allowing interaction with the "Order" collection in the database.

We imported the Request and Response types from the "../types/express" file.
We added the following route handlers:

**addOrderItems:**
This handles the creation of a new order by extracting the order items, shipping address, payment method, etc from the request body. 

It creates a new instance of the Order model with the extracted data and saves it to the database. 
If successful, it returns the created order.

**getOrderById:**
 This retrieves a specific order by its ID from the database. If found, it returns the order details.

**updateOrderToPaid:**
 This updates an order's status to "paid" by modifying the isPaid field to true, setting the paidAt timestamp, and storing payment-related information. It then saves the updated order to the database.

`updateOrderToDelivered:` Similar to the previous function, this updates an order's status to "delivered" by modifying the isDelivered field to true and setting the deliveredAt timestamp. It then saves the updated order to the database.

**getMyOrders:**
This retrieves all orders associated with the currently logged-in user by searching orders based on the user's ID.

**getOrders:**
This retrieves all orders from the database, populated with user information (ID and name).


To be continue...adding the rest part of the article shortly.