Developing a fullstack E- commerce Application with Typescript. Part 1
James Oyanna
Posted on October 14, 2023
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:
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.
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:
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 logsHTTP
requests and responses to the console.
We defined a single route using app.get("/", ...)
that handles GET requests to the root path ("/").
When aGET
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
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
Test your endpoints.
Adding the product Types:
In your types folder, create a file. Name it product.ts and add the following code:
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. 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:
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.
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:
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:
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:
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 theDocument
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:
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:
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.
Posted on October 14, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 23, 2024
August 25, 2024