Chapter 4: Infrastructure for User. MongoDB database. MongoDB index. Jest testing for TypeScript. Your Settings page. API infrastructure for uploading file.
We keep our book up to date with recent libraries and packages.
The section below is a preview of SaaS Boilerplate Book. To read the full text, you will need to purchase the book.
In Chapter 4, you will start with the codebase in the 4-begin folder of our saas repo and end up with the codebase in the 4-end folder.
We will cover the following topics in this chapter:
Infrastructure for User - User Schema and Model. Type interface. - Static methods and Mongoose methods - Express routes, router and API methods
MongoDB database - MongoDB Atlas - Creating MongoDB document - Connecting database - Testing connection
MongoDB index
Jest testing for TypeScript - generateSlug method - Testing generateSlug method
Your Settings page - Form and input - API infrastructure for updating profile
Uploading file API - Page method uploadFile - Getting signed request API - Env variables and CORS settings for uploading file - Uploading file using signed request API - Testing file upload - resizeImage
In Chapter 3, you learned and built "two-app" architecture. You learned new key concepts such as "API method" and "Express route". Here in Chapter 4, we will update our two-project architecture by adding a MongoDB database:
https://docs.atlas.mongodb.com/
In Chapter 3, we harcoded a user object inside an Express route at the api
server
server.get('/api/v1/public/get-user', (_, res) => {
res.json({ user: { email: 'team@builderbook.org' } });
});
In Chapter 4, instead of hardcoding a user object, we will actually retrieve data from the database.
After we connect our api
server to a MongoDB database, the basic data flow (key request-response cycles) will look like this:
In addition to connecting a database, you will learn about the testing framework called Jest:
https://jestjs.io/
We will discuss when and why you would write tests for your code.
We will create a new page called YourSettings
. This page shows basic information about your account and allows you to edit your name and avatar:
Finally, you will learn about building external, or third-party, API infrastructure. In Chapter 3, you built so-called internal API infrastructure that displayed a user's email address on the Index
page.
An internal API infrastructure only invloves the api
server and our database. An external API infrastructure involves an external, third-party server (and database). In this chapter, for example, we will build our first external API infrastructure - we will build infrastructure that uploads a file from our web application to the external AWS S3 service.
Infrastructure for User link
Connecting a MongoDB database to our api
server is not enough for CRUDing (create, read, update, delete) data. We have to write methods that use MongoDB's API methods to CRUD (create, read, update, delete) data. In other words, we have to learn and implement MongoDB CRUD API:
https://docs.mongodb.com/manual/crud/
In this book, instead of using MongoDB CRUD API, we will use an abstraction layer for it, called mongoose
:
https://mongoosejs.com/
In this section, we will get familiar with Mongoose API methds and use some of them for our firtst data model, User.
We will call a method that acts on a data model and uses a Mongoose API method internally, a static method.
In this section, we will work on the part colored red: Express routes, Static methods, and Mongoose methods.
In the next section, we will work on the part colored black: connecting api
server to database.
User Schema and Model. Type interface. link
As we just mentioned, we need to get familiar with Mongoose API since, in this book, we chose not to work with a native Node.js driver for MongoDB:
https://mongoosejs.com/docs/guide.html
The mongoose
library is an abstraction layer built on top of a MongoDB native driver. This means that whenever you call a Mongoose API method, you call a corresponding MongoDB method.
You might ask why you should learn a MongoDB abstraction instead of the native MongoDB driver. A good abstraction library delivers some value on top of the native library. If your data has no well-defined structure and you plan to write your own code to validate data types, then you can use MongoDB driver. However, if your data has well-defined structure (shape), then you should go with Mongoose. The native driver also has higher performance for Mongoose and provides you with a so-called Schema and Model in addition to MongoDB's Document:
https://developer.mongodb.com/article/mongoose-versus-nodejs-driver
The native driver can be significantly faster for some types of queries:
https://medium.com/@bugwheels94/performance-difference-in-mongoose-vs-mongodb-60be831c69ad
In this book, we use Mongoose to work with MongoDB.
MongoDB database stores data as a so-called Document. The format of the Document is BSON. BSON is a binary representation of JSON data:
https://docs.mongodb.com/manual/core/document/
Mongoose Schema allows you to define the shape of the MongoDB Document:
https://mongoosejs.com/docs/guide.html#definition
Simple example of Schema from the above link:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var blogSchema = new Schema({
title: String, // String is shorthand for {type: String}
author: String,
body: String,
comments: [{ body: String, date: Date }],
date: { type: Date, default: Date.now },
hidden: Boolean,
meta: {
votes: Number,
favs: Number
}
});
In Mongoose, a Model is a class
:
https://mongoosejs.com/docs/api.html#model_Model
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
Class
is a special type function (built-in in JavaScript). You can assign parameters and methods to the class
. In the below example, height
and width
are parameters (also called properties), and calcArea
is a method:
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Method
calcArea() {
return this.height * this.width;
}
}
const square = new Rectangle(10, 10);
console.log(square.height);
console.log(square.calcArea());
On your browser, open Chrome Developer Tools
, navigate to the Console
tab, and paste the above code:
You should see 10
and 100
printed in the browser console.
Besides parameters and methods that you define, class
has special built-in methods - for example, the constructor
method. A constructor
is a special method that creates an object with some initial parameters or initial methods:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor
In the above example, we defined the parameters height
and width
.
In the below example, we defined the parameter name
(example from Mozilla docs):
class Polygon {
constructor() {
this.name = "Polygon";
}
}
const poly1 = new Polygon();
console.log(poly1.name);
// expected output: "Polygon"
You created an object poly1
that has the parameter poly1.name
with initial value of "Polygon"
.
You might use the constructor
method if you are familiar with React. In React, if you need to set the initial state or bind some methods to this
of a component or page component, you would use constructor
:
constructor(props) {
super(props);
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
In fact, we used the constructor
method when we defined the Notifier
component inside our app
project. Open book/4-start/app/components/common/Notifier.tsx
and find this block:
constructor(props) {
super(props);
openSnackbarExternal = this.openSnackbar;
}
As you can see, we used constructor
to bind (assign) the method openSnackbar
to this
, an instance of the component Notifier
.
So a Mongoose Model is a class
. How do we create it and extend it with Schema?
In Mongoose, we call the mongoose.model
method to create a subclass of a Mongoose Model:
const User = mongoose.model('User', mongoSchema);
In Mongoose, the mongoose.model
method creates a Model subclass using schema.
An instance of the User
Model is a Mongoose Document, which is also a class. A Mongoose Document represents a one-to-one mapping to documents as stored in a MongoDB database:
https://mongoosejs.com/docs/documents.html
In the below example, user
is an instance of the User
Model, thus a Document:
const User = mongoose.model('User', mongoSchema);
const user = new User();
Since we are using TypeScript, we have to pass data types - we can use interface
- to the above definition of User
:
const User = mongoose.model<UserDocument, UserModel>('User', mongoSchema);
UserDocument
and UserModel
are interfaces
:
https://www.typescriptlang.org/docs/handbook/interfaces.html
TypeScript uses interface
to define data structure, parameters (also called properties), and functions (also called methods).
For example:
interface LabeledValue {
label: string;
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
// expected output: "Size 10 Object"
You've reached the end of the Chapter 4 preview. To continue reading, you will need to purchase the book.
We keep our book up to date with recent libraries and packages.