This is a simple E-shop backend created with the following technologies:
- Express as the underlying framework
- MongoDB as the database technology, integrated with mongoose
- JSON Web Tokens to secure the API, using jsonwebtoken and express-jwt
- multer for file upload, used to upload product images to the server
- bcryptjs to hash passwords
NOTE: This application was created within a week and is not production-ready. It probably has a ton of bugs.
Remember to do every step described below to fully set up the application.
git clone https://github.com/jonisavo/eshop-backend-base.git
cd eshop-backend-base
npm install
The app fetches various information from a .env
file, which should be located in the root folder. You must create it yourself. It looks like this:
API_URL = /api/v1
PORT = 3000
MONGO_CONSTRING = mongodb+srv://...
Where API_URL
is the base url of your API (e.g. http://localhost:3000/api/v1
) and MONGO_CONSTRING
is the credentials used to connect to your MongoDB database. PORT
is the the port used by the server.
The .env
file is ignored by Git. Remove it from the .gitignore
file to be able to check it into version control, but remember to not publish it!
Enter your database connection string to the .env
file as described above. Also, in the MongoDB web client, you must allow your IP address to access the database via the Network > Network Access
menu.
The JSON web tokens are generated using a secret, which is read from a file named jwtRS256.key
. You must create it yourself. It may look something like this:
-----BEGIN RSA PRIVATE KEY-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...
Refer to https://gist.github.com/ygotthilf/baa58da5c3dd1f69fae9 on how to generate a key.
The jwtRS256.key
file is also ignored by Git. Be careful with exposing it to version control.
npm start
If you see
The server is running in port 3000
Database connection is ready...
you are good to go!
{
name: {
type: String,
required: true,
},
color: {
type: String,
default: '',
},
icon: {
type: String,
default: '',
},
image: {
type: String,
default: '',
},
}
{
name: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
richDescription: {
type: String,
default: '',
},
image: {
type: String,
default: '',
},
images: [{
type: String,
}],
brand: {
type: String,
default: '',
},
price: {
type: Number,
required: true,
min: 0,
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category',
required: true,
},
stock: {
type: Number,
required: true,
min: 0,
},
rating: {
type: Number,
default: 0.0,
min: 0.0,
max: 5.0,
},
numReviews: {
type: Number,
default: 0,
min: 0,
},
isFeatured: {
type: Boolean,
default: false,
},
dateCreated: {
type: Date,
default: Date.now,
},
}
{
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
passwordHash: {
type: String,
required: true
},
phone: {
type: String,
default: ''
},
isAdmin: {
type: Boolean,
default: false
},
street: {
type: String,
default: ''
},
apartment: {
type: String,
default: ''
},
zip: {
type: String,
default: ''
},
city: {
type: String,
default: ''
},
country: {
type: String,
default: ''
},
dateRegistered: {
type: Date,
default: Date.now,
},
}
{
orderItems: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'OrderItem',
required: true
}],
shippingAddress1: {
type: String,
required: true
},
shippingAddress2: {
type: String,
default: ''
},
city: {
type: String,
required: true
},
country: {
type: String,
required: true
},
phone: {
type: String,
default: ''
},
status: {
type: String,
required: true,
default: OrderStatus.PENDING
},
totalPrice: {
type: Number,
min: 0,
default: 0
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
dateOrdered: {
type: Date,
default: Date.now
}
}
{
quantity: {
type: Number,
required: true,
min: 0,
max: 99
},
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true
}
}
API_URL/categories
GET /
: get all categoriesGET /:id
: get a single categoryPOST /
: create a new categoryPUT /:id
: update an existing categoryDELETE /:id
: remove an existing category
API_URL/products
GET /
: get all productsGET /?categories=x,y
: filter by given categories
GET /brief
: get all products with only the name and image returnedGET /brief?categories=x,y
GET /:id
: get a single productGET /brief/:id
GET /get/count
: get the count of productsGET /get/featured
: get all featured productsGET /get/featured/:count
: get first:count
featured products
POST /
: create a new productPUT /:id
: update an existing productPUT /:id/gallery
: upload additional images for the productDELETE /:id
: remove an existing product
API_URL/users
GET /
: get all usersGET /:id
: get a single userGET /get/count
: get the user countPOST /
: create a new userPOST /register
: create a new userPOST /login
: log in with an email and passwordPOST /change/password
: change the password of a userPUT /:id
: update an existing userDELETE /:id
: remove an existing user
API_URL/orders
GET /
: get all ordersGET /:id
: get a single orderGET /get/user/:id
: get all orders for the given userGET /get/count
: get total order countGET /get/totalsales
: get total salesPOST /
: create a new orderPOST /:id/set/status/:status
: set order:order
status to:status
PUT /:id
: update an existing orderDELETE /:id
: remove an existing order
All responses are either in the form of
{
"success": true,
"result": {} // can also be an array of objects or a primitive value, see in-depth documentation below
}
or
{
"success": false,
"error": {
"_toString": "message",
"_code": 900
// can also contain other data included in the error
}
}
For all possible error codes for the _code
field, see error_codes.js.
The API is secured with JSON web tokens, which contain the user ID and admin flag. The admin flag is used to determine whether the user should have access to certain functions.
Returns all categories.
Returns a single category with id :id
. Returns a 404
error if none exists.
Requires admin status.
Creates a new category with the provided information.
Requires admin status.
Updates an existing category, changing only the values given. Returns a 404
error if the category does not exist.
Requires admin status.
Removes a category with id :id
. Returns a 404
error if the category does not exist.
Returns a list of all products. The list can be filtered by category with the ?categories=x,y
query parameter.
Example:
GET https://localhost:3000/api/v1/products?categories=6027dce9b0aea13ac46c4299,61274f9bb8aec707ec60ef4d
Returns a list of all products, returning only their name and image fields. The list can be filtered with ?categories=x,y
.
Returns a single product, or a 404
error if none exists.
Returns a single product's name and image fields, or a 404
error if the product does not exist.
Returns the product count. A successful response looks like
{
"success": true,
"result": 5
}
Returns all featured products.
Returns the :count
first featured products.
Requires admin status.
Creates a new product with the provided information. Validates the product's category. An image must be uploaded with the image
key.
Requires admin status.
Updates an existing product, changing only the values given. Returns a 404
error if the product does not exist. The image can be changed with the image
key.
Requires admin status.
Uploads additional images to the product, with to 20 at a time. The images are sent with the images
key.
Requires admin status.
Removes a product with id :id
. Returns a 404
error if the category does not exist.
Requires admin status.
Returns a list of all users. The hashed password is omitted.
Requires authorization.
Returns a single user with id :id
, or a 404
error if none exists. The hashed password is omitted.
The user must be logged in as the user whose information is being accessed. Admins can fetch the information of any user.
Requires admin status.
Returns the total user count.
Requires admin status.
Creates a new user with the provided information. A plaintext password is given in the password
field,
which is then hashed in the backend and stored as the passwordHash
property. Only one user can be created for each e-mail.
A user-facing alias for POST /
.
Log in to the server. Takes an email and plaintext password in the form of
{
"email": "[email protected]",
"password": "password"
}
and replies with a JWT token if authentication was successful:
{
"success": true,
"result": {
"user": "[email protected]",
"id": "...",
"token": "..."
}
}
An error will be returned if the user is already logged in.
Change the password of a user. Takes an email, the current password and new password:
{
"email": "[email protected]",
"currentPassword": "password",
"newPassword": "password2"
}
Fails if:
- User with given e-mail does not exist
- Current password is wrong
- New password is the same as the current password
Requires admin status.
Updates an existing user, changing only the values given. Returns a 404
error if the user does not exist.
Requires admin status.
Removes a user with id :id
. Returns a 404
error if the user does not exist.
Requires admin status.
Returns a list of all orders.
Requires authorization.
Returns a single order with id :id
, or a 404
error if none exists.
An order can be fetched if it has been made by the authorized user. Attempting to fetch an order made by someone else will result in a 404
error. Admins can fetch any order.
Requires authorization.
Returns all orders made by a user with id :id
. If the user is authorized, they can view their own orders. Admins can view anyone's orders.
Requires admin status.
Returns the total count of orders.
Requires admin status.
Returns the total sum of all sales.
Requires admin status.
Creates a new order with the provided information.
Requires admin status.
Changes the order :id
's status to :status
, which can be one of the following:
pending
shipped
cancelled
Requires admin status.
Updates an existing order, changing only the values given. Returns a 404
error if the order does not exist.
Requires admin status.
Removes an order with id :id
. Returns a 404
error if the order does not exist.