Guided Exercise - Online Bookstore - Backend - Express + MongoDB Backend Exercise: Online Bookstore API #192
Replies: 6 comments 1 reply
-
Detailed ExplanationLet's delve into the details: Dependencies
Web Development Concepts
Security Concepts
Development Process
I hope this serves as a comprehensive guide to understanding the various elements, dependencies, and concepts involved in the complete Now let's go deeper into each topic. Dependencies: Deeper Dive
Web Development Concepts: Deeper Dive
Security Concepts: Deeper Dive
Development Process: Deeper Dive
This deeper dive aims to provide a comprehensive understanding of the various elements involved in building a secure, efficient, and scalable web application. |
Beta Was this translation helpful? Give feedback.
-
|
Here's a detailed guide to run and test all functionalities of the hypothetical Express + MongoDB backend application, focusing on features like authentication, authorization, logging, middlewares, and security. Since we haven't actually created a specific application, the following examples are to be used as a generic guide. How to Run the ApplicationStep 1: Install DependenciesFirst, navigate to your project directory and install the required dependencies using npm or yarn. npm installStep 2: Start MongoDBEnsure that MongoDB is running. mongodStep 3: Start the ServerStart the server by running: npm startHow to Test FunctionalitiesUser Registration:
{
"username": "john_doe",
"password": "123456",
"email": "john@example.com"
}
User Login:
{
"username": "john_doe",
"password": "123456"
}
Fetch User Profile (Authorized):
{
"Authorization": "Bearer <Your_JWT_Token>"
}
Update User Profile (Authorized):
{
"Authorization": "Bearer <Your_JWT_Token>"
}
{
"email": "john_new@example.com"
}
Create Post (Authorized):
{
"Authorization": "Bearer <Your_JWT_Token>"
}
{
"content": "This is a new post."
}
Fetch Posts:
Like a Post (Authorized):
{
"Authorization": "Bearer <Your_JWT_Token>"
}
Add a Comment to Post (Authorized):
{
"Authorization": "Bearer <Your_JWT_Token>"
}
{
"comment": "This is a comment."
}
Testing with Jest
npm test
Each test should cover these basic scenarios, plus edge cases like invalid data formats, unauthorized access, and so forth. Make sure to mock your database using a package like This guide aims to provide a comprehensive methodology to run and test the backend application thoroughly, covering all major scenarios. |
Beta Was this translation helpful? Give feedback.
-
SolutionLet's start by setting up a clear folder structure for our project: Step 1: Project Setup In your terminal: mkdir online-bookstore
cd online-bookstore
npm init -y
npm install express mongoose jsonwebtoken bcryptjs dotenv morgan validator helmet express-rate-limit winstonCreate a Step 2: Establish MongoDB Connection In const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const app = express();Step 3: Basic Express Setup Continue in app.use(express.json());
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});Step 4: Model Definitions In const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
username: String,
password: String,
role: String,
});
userSchema.pre('save', function(next) {
this.password = bcrypt.hashSync(this.password, 12);
next();
});
module.exports = mongoose.model('User', userSchema);Step 5: Authentication In const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const User = require('../models/User');
const router = express.Router();
router.post('/login', async (req, res) => {
const user = await User.findOne({ username: req.body.username });
if (!user) return res.status(400).send('Invalid username or password.');
const validPassword = bcrypt.compareSync(req.body.password, user.password);
if (!validPassword) return res.status(400).send('Invalid username or password.');
const token = jwt.sign({ _id: user._id, role: user.role }, process.env.JWT_SECRET);
res.send(token);
});
module.exports = router;Step 6: Authorization Middleware In const jwt = require('jsonwebtoken');
function authorize(role) {
return (req, res, next) => {
const token = req.header('x-auth-token');
if (!token) return res.status(401).send('No token provided.');
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (!decoded || (role && decoded.role !== role)) return res.status(403).send('Forbidden.');
req.user = decoded;
next();
}
}
module.exports = authorize;Step 7: CRUD for Books In const mongoose = require('mongoose');
const bookSchema = new mongoose.Schema({
title: String,
author: String,
price: Number,
});
module.exports = mongoose.model('Book', bookSchema);In const express = require('express');
const Book = require('../models/Book');
const authorize = require('../middleware/authorization');
const router = express.Router();
// All your CRUD operations here, e.g.,
router.get('/', async (req, res) => {
const books = await Book.find();
res.send(books);
});
// Use the authorize middleware as needed, e.g.,
router.post('/', authorize('admin'), async (req, res) => {
const book = new Book(req.body);
await book.save();
res.send(book);
});
module.exports = router;Step 8: Logging Middleware In const morgan = require('morgan');
app.use(morgan('tiny'));For error-handling, logging, input validation, security, rate limiting, user registration, and advanced logging, follow the same method to break down your code into modular sections and use middleware appropriately. Remember to use the Let's dive deeper and continue elaborating on the code. Alternate Step 8: Logging Middleware You've already added the In const morgan = require('morgan');
app.use(morgan('combined')); // 'combined' gives detailed logsStep 9: Error-handling Add a centralized error-handling middleware at the bottom of your app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something went wrong!');
});Step 10: Input Validation In const { check, validationResult } = require('express-validator');
router.post('/',
[
check('title').isLength({ min: 1 }).withMessage('Title is required'),
check('author').isLength({ min: 1 }).withMessage('Author is required'),
check('price').isNumeric().withMessage('Valid price is required'),
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// ... rest of the CRUD logic ...
}
);Part-2 Step 11: Securing the Application You've already added In const helmet = require('helmet');
app.use(helmet());Step 12: Rate Limiting Again, in your main const rateLimit = require("express-rate-limit");
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);Step 13: User Registration and Validation In router.post('/register',
[
check('username', 'Username is required').not().isEmpty(),
check('password', 'Password should be at least 6 chars long').isLength({ min: 6 }),
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const userExists = await User.findOne({ username: req.body.username });
if (userExists) return res.status(400).send('Username already exists.');
const user = new User(req.body);
await user.save();
res.send('User registered successfully!');
}
);Step 14: Testing with Postman At this point, you would open Postman and test the following endpoints:
Step 15: Logging and Monitoring For advanced logging, use In const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'online-bookstore-service' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// To log directly to the console in a development setting:
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple(),
}));
}
// Replace console.error with logger.error and likewise for other log levelsNow, when you want to log anything, you can use This provides a comprehensive backend setup. Remember, in a real-world application, you would also want to set up proper HTTPS, ensure your environment variables are secure, and perhaps consider using a service like MongoDB Atlas for a more secure and scalable MongoDB solution. |
Beta Was this translation helpful? Give feedback.
-
Docker and WebSocketsI'll begin by explaining what they are and then dive into how they can be integrated with the app. DockerWhat is Docker?Docker is a platform that allows developers to create, deploy, and run applications in containers. Containers allow a developer to package an application with all parts it needs, such as libraries and other dependencies, and ship it all out as one package. This ensures that the application will run the same regardless of where it's being run. Setup Docker in Project
Create a FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5000
CMD [ "node", "server.js" ]Then, create a To build and run the Docker container: docker build -t online-bookstore-backend .
docker run -p 5000:5000 online-bookstore-backend
Similar to the backend, create a FROM node:14 as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:1.17.1-alpine
COPY --from=build-stage /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]Build and run: docker build -t online-bookstore-frontend .
docker run -p 80:80 online-bookstore-frontendWebSocketsWhat are WebSockets?WebSockets provide a full-duplex communication channel over a single, long-lived connection. They're designed to work over the same ports as HTTP and HTTPS and share similar handshake processes. This makes them great for real-time updates in applications. Integrating WebSockets in Project:
Install npm install socket.ioUpdate the server initialization in the backend: const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
io.on('connection', (socket) => {
console.log('New client connected');
// Sample: Emitting to the frontend
socket.emit('message', 'Welcome to Online Bookstore!');
socket.on('disconnect', () => {
console.log('Client disconnected');
});
});
server.listen(5000, () => console.log('Server running on port 5000'));
Install npm install socket.io-clientIntegrate with React: import { useEffect } from 'react';
import io from 'socket.io-client';
const socket = io('http://localhost:5000');
function App() {
useEffect(() => {
socket.on('message', (message) => {
console.log(message);
});
return () => {
socket.off();
};
}, []);
// ... rest of the App component
}Real-world application:
This is a basic introduction and integration guide. There are advanced topics like Docker Compose for managing multi-container Docker applications and advanced uses of WebSockets for handling real-time data at scale. Both Docker and WebSockets are vast topics, and their implementation can differ based on the complexity of your application and your specific needs. Let's expand on integrating Docker Compose for our full-stack application and further enhance our WebSocket use-case for the bookstore. Docker ComposeWhat is Docker Compose? Docker Compose is a tool for defining and running multi-container Docker applications. With Docker Compose, you can set up an entire environment, including the backend, frontend, database, and any other services, all with a single Integrating Docker Compose:
version: '3'
services:
backend:
build:
context: ./path-to-backend
dockerfile: Dockerfile
ports:
- "5000:5000"
depends_on:
- mongo
frontend:
build:
context: ./path-to-frontend
dockerfile: Dockerfile
ports:
- "80:80"
mongo:
image: mongo:latest
ports:
- "27017:27017"
volumes:
- mongodata:/data/db
volumes:
mongodata:
This allows you to define and manage all parts of your application infrastructure in one place, ensuring they are interconnected correctly. Advanced WebSocket Use-case for Bookstore:Real-time Book Stock Update: Imagine this scenario: A limited stock of a new book is released, and many users are browsing the book's details simultaneously. With WebSockets, you can notify all connected users in real-time if the book goes out of stock.
Modify the book purchasing or stock update endpoint: app.post('/api/books/:id/purchase', (req, res) => {
// After processing the purchase or stock update...
// Check if the book is now out of stock.
if (book.stock === 0) {
io.emit('book-out-of-stock', book.id);
}
res.send("Purchase completed");
});
Update the import { useEffect } from 'react';
import io from 'socket.io-client';
const socket = io('http://localhost:5000');
function BookPage() {
useEffect(() => {
socket.on('book-out-of-stock', (bookId) => {
if (currentBookId === bookId) {
alert('This book just went out of stock!');
}
});
return () => {
socket.off('book-out-of-stock');
};
}, [currentBookId]);
// ... rest of the BookPage component
}With this, every time a book goes out of stock, all users currently viewing or interested in that book get a real-time notification, enhancing user experience and ensuring customers are well-informed. These integrations show how both Docker Compose and WebSockets can be valuable tools in building and deploying modern web applications. Docker Compose simplifies the management of multi-container apps, while WebSockets enable real-time features that can significantly improve user interaction. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
|
Online-bookStore: Backend |
Beta Was this translation helpful? Give feedback.








Uh oh!
There was an error while loading. Please reload this page.
-
Express + MongoDB Backend Exercise: Online Bookstore API
Part-1
This exercise will help you create a practical Express.js and MongoDB backend with features like authentication, authorization, logging, middleware, and security. You'll build an API for an online bookstore.
Prerequisites:
Application Features:
Step 1: Project Setup
Initialize a new Node.js project and install the required packages.
Create a
.envfile for environment variables like MongoDB URI, JWT secret, etc.Step 2: Establish MongoDB Connection
Use Mongoose to connect to MongoDB in your main
app.jsfile.Rationale: Mongoose provides a straight-forward, schema-based solution to model your application data.
Step 3: Basic Express Setup
Create your main Express application.
Step 4: Model Definitions
Define your Mongoose models for Users and Books in separate files.
Rationale: We're using bcrypt.js to hash passwords before storing them, as plain text passwords are a security risk.
Step 5: Authentication
Implement JWT-based authentication. Create a
/loginendpoint to issue tokens.Rationale: JWT is stateless, meaning that you don't have to store the token on the server, making it scalable and easy to use.
Step 6: Authorization Middleware
Create a middleware to protect routes based on user roles.
Step 7: CRUD for Books
Create CRUD operations for books and use the authorization middleware to protect the routes.
Rationale: CRUD operations form the backbone of most web APIs.
Step 8: Logging Middleware
Create a middleware for logging using the morgan library.
Rationale: Logging helps in debugging and monitoring.
Step 9: Error-handling
Implement centralized error-handling middleware.
Rationale: This ensures that all errors are caught and handled appropriately, improving your application's robustness and user experience.
Step 10: Input Validation
Use the
validatorlibrary for basic input validation.Rationale: Input validation is essential for application security to protect against malicious user data.
That's it! This exercise covers a lot of ground, but it provides a strong foundation in backend development with Node.js, Express, and MongoDB. Make sure to test all your endpoints using Postman or a similar API client.
Part-2
Step 11: Securing the Application
Add basic security measures to the application.
Rationale: Security is crucial in every application, especially when handling sensitive information like user credentials.
Install
helmetfor setting security-related HTTP headers:In your main
app.js:Step 12: Rate Limiting
To protect against brute-force attacks, implement rate limiting.
Rationale: Rate limiting ensures that a user cannot make unlimited API requests, thereby reducing the risk of brute-force attacks.
Install
express-rate-limit:Then set it up:
Step 13: User Registration and Validation
Add an endpoint for user registration and apply input validation and password hashing.
Rationale: User registration is a common feature in many apps, and proper validation and hashing ensure data integrity and security.
Step 14: Testing with Postman
Now that your application is feature-rich, test all its functionalities using Postman.
Rationale: Testing ensures that the application works as expected and can handle edge cases.
Step 15: Logging and Monitoring
Consider using a more advanced logging solution for production. You might use libraries like
winstonfor this.Rationale: Advanced logging solutions provide more features like logging to a file, logging levels, and more.
Step 16: Final Code Review
Review your code to make sure everything is working as expected and that you've followed best practices for coding standards, file structure, and naming conventions.
Rationale: This ensures maintainability and readability of the codebase, making future updates easier.
Step 17: Deployment
Prepare your code for deployment. Check environment variables, and secure sensitive data.
Rationale: Proper deployment practices ensure that the application runs smoothly in a production environment.
Testing Your Application
Now that your application is built, you should consider writing automated tests for it. Testing is a critical aspect of modern web development that ensures your application works as expected.
That's it! You've built a comprehensive and secure backend service with Node.js, Express, and MongoDB. Feel free to extend this application by adding more features, creating more robust validations, or even integrating more advanced authentication and authorization mechanisms.
Part-3
Step 18: Integrating Unit Testing with Jest
Now, let's add unit testing to ensure our Express + MongoDB application is working as expected.
Rationale: Unit testing helps to catch errors early in the development process, making it easier and cheaper to fix them.
Setup Jest and Required Packages
First, install Jest,
supertestfor HTTP assertions, andmongodb-memory-serverto run an in-memory MongoDB instance:Update your
package.json:Create a Jest Setup File
Create a file called
jest.config.jsin your root directory and add the following:Rationale: This configuration tells Jest to use a Node environment for testing.
Mock Test Cases
Let's consider some mock test cases for our application:
Writing the Tests
Create a new folder in your root directory called
__tests__.Create a file called
user.test.js:Add these tests to
user.test.js:Create a file called
auth.test.js:Run your tests:
npm testBenefits of Unit Testing
That's it! You've now integrated Jest for unit testing with your Express + MongoDB application, making it more robust and maintainable.
Beta Was this translation helpful? Give feedback.
All reactions