Node.js - Express.js

From Sinfronteras
Revision as of 15:01, 23 January 2019 by Adelo Vieira (talk | contribs) (Creating a new TypeScript Node.js project)
Jump to: navigation, search

Material del curso

Curso online que recomendó Remo: https://javascript30.com/

https://courses.wesbos.com/account/access/5bc5c20a93bbc3208321f21b


Guía 1 - Setting up a Node.js + TypeScript development environment: https://docs.google.com/document/d/15or9Ehxyu0AzTvYHW-LrsVxqZcLY30JjaCGhXAVmmK4/edit#

TypeScript

TypeScript is an open-source programming language developed and maintained by Microsoft. It is a strict syntactical superset of JavaScript, and adds optional static typing to the language.

TypeScript is designed for development of large applications and transcompiles to JavaScript. As TypeScript is a superset of JavaScript, existing JavaScript programs are also valid TypeScript programs. TypeScript may be used to develop JavaScript applications for both client-side and server-side.

To get started with TypeScript installation, you need to first install Node.js in your system.

Node.js

Install Node.js https://nodejs.org/en/download/ https://en.wikipedia.org/wiki/Node.js

Node.js is an open-source, cross-platform JavaScript run-time environment that executes JavaScript code outside of a browser. Historically, JavaScript was used primarily for client-side scripting, in which scripts written in JavaScript are embedded in a webpage's HTML and run client-side by a JavaScript engine in the user's web browser. Node.js lets developers use JavaScript to write Command Line tools and for server-side scripting-running scripts server-side to produce dynamic web page content before the page is sent to the user's web browser. Consequently, Node.js represents a "JavaScript everywhere" paradigm, unifying web application development around a single programming language, rather than different languages for server side and client side scripts.

Installation

https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions

curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs


Alternatively, for Node.js 10:

curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt-get install -y nodejs


You can check that Node.js was installed correctly by executing the following command in Bash:

node -v


Node package manager - npm

The Node.js installer will install the node command in your machine but it will also install the Node package manager (npm) command. You can also test that npm has been installed correctly by executing the following command:

npm -v

We can use the npm command to install third-party Node.js packages. The npm command allows us to install a few different kinds of dependencies:

  • Global dependencies: Shared across projects. They are installed in our development environment, outside of a project. An example of this kind of dependencies is a CLI tool such as tsc command because we will invoke fro our CLI, not from a project.
  • Local Dependencies: This kind of dependencies are installed locally, inside of a project. Not shared across projects. They are needed during the execution of the application; in other words, after the application has been deployed and the development phase has been completed. An example is a framework or library that we use to build our application such as Express.js.
  • Development dependencies: Local, not shared across the project and needed only during development. An example is a development tool that we use only during the development phase such as a testing library.


We can install npm dependencies using the following commands:

  • Install all the dependencies in the package.json file:
npm install
  • Install a global dependency:
npm install -g NAME_OF_THE_DEPENDENCY
  • Install a local dependency:
npm install --save NAME_OF_THE_DEPENDENCY
  • Install a local development dependency:
npm install --save-dev NAME_OF_THE_DEPENDENCY

You can learn more about npm by reading the official documentation at https://docs.npmjs.com

Installing TypeScript and ts-node

TypeScript and ts-node can be installed using the Node Package Manager (NPM). We will install them as global dependencies:

npm install -g typescript
npm install -g ts-node

Because we have installed both dependencies as global dependencies these will be shared across all projects in our computer. This means that we only need to run these commands once.

You can learn more about npm by reading the official documentation at https://docs.npmjs.com


After installingTypeScript, the command tsc should become available:

tsc -v

Also:

ts-node -v

Git Bash in Windows

Si estamos en Windows, tenemos que instalar Git Bash in Windows; que es tan sólo un terminal Bash en Windows.

Visual Studio Code

https://code.visualstudio.com/download/

https://en.wikipedia.org/wiki/Visual_Studio_Code

Visual Studio Code (VS Code) is a source code editor developed by Microsoft for Windows, Linux and macOS. It includes support for debugging, embedded Git control, syntax highlighting, intelligent code completion, snippets, and code refactoring.

Installation

Download the <file>.deb: https://code.visualstudio.com/download Installation: https://code.visualstudio.com/docs/setup/linux

Custom icons in VS Code

There is an extension in VS Code that displays custom icons for different types of files. For example, for a JavaScript file with the “.js” extension it displays the JavaScript logo while for a TypeScript file with the “.ts” extension it displays the TypeScript logo. This can be useful because it helps your brain to visually identify each of the files in the project explorer.

You can install the extension by clicking on the Extensions icon. You can then search for vscode-icons and install it.

After installing it you will need to restart VS Code and click on File → Preferences → File Icon Theme. Then select vscode-icons.

Postgres

https://www.postgresql.org/

https://en.wikipedia.org/wiki/PostgreSQL

PostgreSQL, often simply Postgres, is an object-relational database management system (ORDBMS) with an emphasis on extensibility and standards compliance.

Install Docker CE on Ubuntu

Docker is a technology that allows us to run containers. A container is a self-contained environment. A container can contain aspecific operating system, software packages, and configuration.

We are going to install the Docker community edition (AKA Docker CE).

https://store.docker.com/search?type=edition&offering=community

https://docs.docker.com/install/linux/docker-ce/ubuntu/

Please note that you will be required to create an account. After installing Docker, you might have to restart your computer.

Setting environment variables

In Linux and MacIn Mac and Linux we can use the following commands in Bash to create environment variables:

export DATABASE_USER=postgres
export DATABASE_PASSWORD=secret
export DATABASE_HOST=localhost
export DATABASE_PORT=5432
export DATABASE_DB=demo

Postgres docker installation

After installing Docker, you are going to need to download the Docker image for Postgres 9.5. You can do this using the following command:

sudo docker pull postgres:9.5

You can then use the following command to see the list of all the Docker images installed on your computer:

sudo docker images

The Postgres 9.5 image should be displayed in the list. At this point, we need to run a Docker container. Each container is arunning instance of an image. We can have multiple containers running of a given image, but this time we will only run onecontainer because we don't need two instances of Postgres. We can create a Docker container with the Postgres image using thefollowing command:

sudo docker run --name POSTGRES_USER -p "$DATABASE_PORT":"$DATABASE_PORT" -e POSTGRES_PASSWORD="$DATABASE_PASSWORD" -d postgres

Docker basics

We have already learned how to download Docker images using the dockerpull command and how to create a container using the «docker run command». We will now learn about other basic Docker commands. We can use the «docker ps» command to see all the containers in our machine:

docker ps -a

We should be able to see the Postgres container running.

Output of the docker pa -a command

Each container and image has an associated ID that looks like the following:

c6f7dfc1a4c8

We can use the following command to stop a running Docker container:

sudo docker stop INSERT_CONTAINER_ID_HERE
sudo docker stop c6f7dfc1a4c8

Similarly, we can run a Docker container using the start command:

sudo docker start c6f7dfc1a4c8

Please note that the run command is used to create a new Docker container while the start command is used to run an existingDocker container given its ID.

We can use the following command to remove a Docker container:

sudo docker rm c6f7dfc1a4c8

Please note that before you can remove a Docker container, you must stop it. The following command can be used to remove aDocker image:

docker rmi INSERT_IMAGE_ID_HERE

Please note that before you can remove a Docker image, you must stop and remove all its associated containers.

Connecting to Postgres from SQLectron

After starting a Postgres Docker container, we are going to try to connect to it from an external tool to make sure that everything isOK. The tool that we are going to use is known as SQLectron. You can download SQLectron from:

https://github.com/sqlectron/sqlectron-gui/releases/tag/v1.30.0

You need to download the binary for your operating system.

After installing SQLectron and opening it. We need to create a new database connection using the add button:

Figure 23: The SQLectron home page

A form will be displayed on the screen. We need to use the same details that we used in our environment variables and clicktest to check that they work. We will have a problem because we have set the database name environment variableDATABASE_DB as demo and by default, Postgres doesn’t have a database called demo

Please note that Postgres is going to be our database server but a database server can contain multiple database instances bydefault Postgres contains a database instance named postgres so we will use the default database to connect to the databaseserver for the very first time:

Figure 24: Creating a connection in SQLectron

We can click on the test button to check that we can connect. Please note that the Docker Postgres container must be alreadyrunning as explained in the preceding section. Once you manage to see Successfully connected you can click on save. After saving the new saved connection should be displayed on the screen:

Figure 25: A saved connection in SQLectron

We need to click on the new database connection to open a new SQL editor. We can then write by hand an SQL statement tocreate the "demo" database:

CREATE DATABASE demo;

Figure 26: SQL editor in SQLectron

After executing the preceding command, we need to disconnect from the database using the top right icon. We need to the returnto the screen that allows us to select an existing connection:

Figure 27: A saved connection in SQLectron

This time instead of opening the connection we need to edit it. We are going to update the connection details to use the demo database instead of the postgres database.

Figure 28: Creating a connection in SQLectron

Once more we can use the “test” button to ensure that everything is correct. If you cannot connect you should first ensure that theDocker container is running using the dockerps-a command. If everything is correct, the status column should show how longthe container has been running.

Using the Google Chrome developer tools

We are going to use the Google Chrome developer tools to investigate the potential cause of errors and to call the server-side APIs that we will implement using Express. There are three ways to access the Chrome developer tools:

  • Clicking on the top right menu item then going to → More Tools → Developer tools.
  • Press F12
  • Press Ctrl+ Shift + I

Exploring HTTP requests

We can use the Chrome developer tools to examine all the HTTP request send from the browser. We can do this by accessing the network tab after opening the developer tools. We will then be able to see all the HTTP requests in a list:

Exploring_HTTP_requests_using_the_Google_Chrome_developer_tools

If nothing is displayed is most likely because you need to reload the page after opening the developer tools. The network tab also allows us to filter the HTTP request by the content type. For example, if we click in XHR the developer's tools will only display the HTTP requests performed as an AJAX request.

When we click on one of the HTTP requests we are able to explore the details about the requests. Including details such as the request headers, response headers, and status code.

You can learn more about the Chrome developer tools network tab at https://developers.google.com/web/tools/chrome-devtools/network-performance

Executing an HTTP call from the Chrome console

We use ts-node to run our Express applications. However, sometimes we want to call one of the REST web services that we have implemented. For example, we might want to perform the following AJAX call:

(async () => {
  const response = await fetch(
    "http://www.remojansen.com/website/js/models/awards.json"
  );
  const json = await response.json();
  console.log(json);
})();

We use the fetch function to perform an AJAX call. The best thing about fetch is that it returns a Promise, which means that we can use async/await instead of the Promise.then() method. The only problem is that our browser might not be compatible with async/await because it is one of the latest JavaScript features. So we want to compile this code into JavaScript to avoid potential compatibility issues. We can do this using the tsc command but it is a bit tedious. So instead of that, it is handy to use the TypeScript online compiler (AKA the TypeScript playground): http://www.typescriptlang.org/play/

After we write the TypeScript code we can copy the JavaScript ouput from the right-side editor. We can then open the Chrome developer tools and select the console tab. We can then paste the JavaScript code and press enter to execute it.

You can learn more about the Chrome developer tools console at https://developers.google.com/web/tools/chrome-devtools/console

TypeScript online compiler

TypeScript online compiler (AKA the TypeScript playground): http://www.typescriptlang.org/play/

Creating and Compiling a new TypeScript project

Creating a new TypeScript project

Each new TypeScript project requires a new empty folder. It is recommended to create a new folder under your home directory. I call this directory "CODE" and inside it, I create a new empty folder for each project.

A TypeScript project could be a Node.js project but it could also not be a Node.js project. For example, it could be an Angular or React.js project. In this section, we are going to focus on the steps required to create a TypeScript project independently of the kind of application that we will build.

All our TypeScript projects are going to need the following:

1- A new empty directory
2- A TypeScript compiler configuration file (tsconfig.json)
tsc --init
3- A npm configuration file (package.json)
npm init --y
4- At this point, we are ready to open the project using VS Code. To open a project in VS Code we need to open VS Code and select File → Open Folder...
5- After opening the folder in VS Code, we need to create a new folder. This time we will use the VS Code feature that allows us to create a new folder. I’m going to name the folder src.
6- Then I will create a new file named demo.ts using VS Code. The file will be contained inside the src directory.
7- Finally, I will add some content to the demo.ts file. In this case, we are going to add just one line of code:
console.log("Hello world!");
VisualStudio TypeScript project.png

You will need to repeat these steps everytime you create a new TypeScript project. It is very important to use a new empty folder to avoid potential configuration issues.

Compiling and running a TypeScript project

Now that we have created the basic structure of a TypeScript project we are going to compile it and execute it. To compile the TypeScript project we need to use the tsc command and the project flag to specify the TypeScript compiler configuration file:

tsc -p tsconfig.json


If everything goes well this will generate a new file named demo.js right next to the existing demo.ts file. We can then execute the JavaScript file demo.js using the node command:

node ./src/demo.js


Please note that the node command can only be used to execute JavaScript files (.js) not TypeScript files (.ts) this means that we must compile our application before we can run it. This requires two steps and it is a bit tedious. However, we can use the ts-node command to do the two steps with one single command:

ts-node ./src/demo.ts

Note that the ts-node command can be used with TypeScript files.

Creating a new TypeScript-Node.js project

We have learned how to create a basic TypeScript project. However, if we are going to work on a Node.js application, we must perform a few additional steps. First, we need to install the Node.js type definitions. We can do this using npm:

npm install --save-dev @types/node

The Node.js type definition should allow us to import the Node.js core modules. For example, we can import the file system module as follow:

import * as fs from "fs";

We are also going to open the tsconfig.json and uncomment the "lib" setting and add the following value:

"lib": [ "es6" ]

The preceding should allow us to work with some of the JavaScript APIS of the ES6 spedification such as the Promise API.

Express.js

We now know how to create a generic TypeScript Node.js project, but we need to be a little more specific. We are going to focus on bulding Node.js using a framework know as Express.js.

To be able to use Express, we need to install the express module and its type definitions @types/express:

npm install --save express
npm install --save-dev @types/express

We can then import express as follows:

import express from "express";

This will fail if you forget to install both modules.

HTTP web services with Express.js

We are going to use Express.js to declare REST Web Services. A web service is a piece of code that can be invoked remotely by a consumer (known as the client). We are going to build web services using the HTML protocol. We will use a unique URL for each web service and we will use HTTP requests and responses (with headers and body) to transfer data between the client and the server.

It is very important to understand that we are going to use Express.js as a framework that helps us to implement web services. This means that Express is used to implement the server part (not the client).

When we are working with Express, we will declare web services that always receive HTTP requests and answer with HTTP responses. Express will never receive a response because that only happens in the client.

Express will run as a process in our machine. We will execute this process using the terminal. However, the client will be just some code that we will execute in our web browser.

Please refer to the An Introduction to API's by Gonzalo Vázquez article if you need additional help to understand the HTTP protocol and Web APIs as a concept.

Express Hello World

At this point, we should be able to create a very basic Express application and execute it to see if everything has gone right so far. We are going to change the content of the demo.ts file from:

console.log("Hello world!");

To:

// Import express module
import express from "express";

// Declare a new express application
const app = express()

// Declare port to be used by the server
const port = 8080

// Declare endpoint for HTTP GET /
app.get('/', (req, res) => res.send('Hello World!'));

// Start HTTP server in port 8080
app.listen(
  port,
  () => console.log(`Example app listening on port ${port}!`)
);


We can then the run the Express application using:

ts-node ./src/demo.ts


If everything goes well the following should be displayed in bash:

Example app listening on port 3000!


You can then open Google Chrome and visit http://localhost:3030 and if everything went well you should see the following on the screen:

Hello World!

The preceding code snipped creates an HTTP server and starts running it. Once the server is running you cannot use the bash anymore unless you kill the process. You can kill the process by pressing Ctrl + c when focus on the bash.

Please note that if you change the code you will need to:

  • Save the changes
  • Kill the process using Ctrl + c
  • Rerun it with ts-node

Later in this document, we will learn a way to automatically re-load the server when the code changes but it is important to know how to do it by hand.

The previous code snippet represents the minimum amount of code required to start an Express application. The code snippet declares a new express application and our very first web service. The address path “/” will return “Hello World!” for HTTP reuquests with GET as its method. This is our very first web service. The Express application then starts running in the port 8080.

Automatically restart the server when the code changes

Install an npm module known as nodemon:

npm install -g nodemon

Create a nodemon.json file with the following content:

{
  "watch": ["src"],
  "ext": "ts",
  "ignore": ["src/**/*.spec.ts"],
  "exec": "ts-node ./src/demo.ts"
}


Run the application using nodemon:

nodemon

The nodemon process will use the configuration from the nodemon.json file. This configuration will watch changes in the src folder and execute the ts-node ./src/demo.ts command every time there is a change. By doing this you will not need to worry about manually killing the server and starting it again everytime you change a file.

Express Hello World

Project - Implementing a REST API with TypeScript and Node.js

https://docs.google.com/document/d/1kV9n057BnuJ_ToU7sZ9ZKqo9rXakYp_7KdqkpETnefo/edit#

Please note that this CA will be continued by a second CA at the end of the second semester. The second CA will consist of a React frontend application and will use the HTTP REST API implemented as part of this CA.

Setting up the project

(5%, Marks)

In this project, we are going to implement the REST API for a for a simplified version of the website Reddit. The user will be able to create an account/ login and post links. Other users will then be able to upvote and downvote the links.

The application should be implemented using the technologies that we used in class: TypeScript, Node.js, Express.js, TypeORM, and Postgres.

To complete this practice you are going to need to install Node.js, TypeScript, and ts-node. You will also need to create a package.json file and a tsconfig.json file. You are going to need to change the default configuration in the tsconfig.json as we have been doing in class.

Please remember to refer to the guides shared in Moodle if you need additional help:

You project directory architecture should look as follows:

├───node_modules
│   └─── ...
├───package.json
├───tsconfig.json
├───app.ts
├───index.ts
├───db.ts
│   ├───backend
│   │   ├───controllers
│   │   ├───entities
│   │   ├───middleware
│   │   └───repositories
│   └───frontend
│       └─── client.ts
└───test
    └───controllers

Entities

  • Create a file named: /src/backend/entities/link.ts
This file should define the Link entity. It should contain an ID, a reference to the User an URL and a title.
  • Create a file named: /src/backend/entities/user.ts
This file should define the User entity and it must contain an ID, an email and a password.
  • Create a file named /src/backend/entities/vote.ts
This file should define the Vote entity and it should contain an ID, a reference to the user, a reference to the Link and a boolean flag that indicates if the vote is positive or negative.
  • Create a file named /src/backend/entities/comment.ts
This file should define the Comment entity and it should contain an ID, a reference to the user, a reference to the Link and a text field for the comment content.

Repositories

You must implement 3 repositories (one for each of the previously defined entities) under the /src/backend/repositories/ directory. The recommended database for this assignment is Postgres.

Controllers

(45%, 45 Marks)

We are going to need two controllers. Each endpoint has different requirements but all endpoints share the following set of common requirements:

  • In all your controllers, the inputs of each endpoint should be validated and an error HTTP 400 (Bad Request) should be returned if the inputs are invalid.
  • If an exception takes place an error HTTP 500 (Internal Server Error) should be returned.
  • When a user doesn't have permissions to perform an operation, an error HTTP 403 (Forbidden) should be returned.
  • When a request tries to access a private endpoint without a known identity, an error HTTP 401 (Unauthorized) should be returned.

Links controller

Create a new controller under the /src/backend/controllers/links_controller.ts directory. The links controller requires the following endpoints:

HTTP Method URL Description Is public
/api/v1/links GET Returns all links Yes
/api/v1/links/:id GET Returns a link and its comments Yes
/api/v1/links POST Creates a new link No
/api/v1/links/:id DELETE Deletes a link No
/api/v1/links/:id/upvote POST Upvotes link No
/api/v1/links/:id/downvote POST Downvote slink No
  • GET /api/v1/links is public and takes no arguments.
  • POST /api/v1/links requires user authentication and takes a link in the request body. It should return the new link.
  • DELETE /api/v1/links/:id requires user authentication and takes the id of a link via the request URL. A user should not be able to delete a link if he is not the owner of the link.
  • POST /api/v1/links/:id/upvote requires user authentication and takes the id of a link via the request URL. A user should not be able to vote the same link multiple times.
  • POST /api/v1/links/:id/downvote requires user authentication and takes the id of a link via the request URL. A user should not be able to vote the same link multiple times.


Auth controller

Create a new controller under the /src/backend/controllers/auth_controller.ts directory. The auth controller requires the following endpoints:

HTTP Method URL Description Is public
/api/v1/auth/login POST Returns an auth token Yes
  • POST /api/v1/auth/login is public and takes the user email and password as JSON in the request body. It returns a JWT token as a response.


User controller

Create a new controller under the /src/backend/controllers/users_controller.ts directory. The user controller requires the following endpoints:

HTTP Method URL Description Is public
/api/v1/users POST Creates a new user account Yes
/api/v1/users/:id GET Returns and user with all its activity (links and comments) Yes
  • POST /api/v1/users is public and takes the user email and password as JSON in the request body. It returns the new user as a response. An error 400 should be returned if the user email is already used by another account.
  • GET /api/v1/users/:id it is public and it takes the user ID via the URL. It should return 404 if the user is not found. If the user is found it should return not just the user but also its links and comments.


Comment controller

Create a new controller under the /src/backend/controllers/comment_controller.ts directory. The comment controller requires the following endpoints:

HTTP Method URL Description Is public
/api/v1/comments POST Creates a new comment No
/api/v1/comments/:id PATCH Updates the content of the comment No
/api/v1/comments/:id DELETE Deletes a comment No
  • POST /api/v1/comments it is private and allows us to create a new comment by sending it in the request body.
  • PATCH /api/v1/comments/:id it is private and allows us to edit an existing comment by its ID. The updated content will be sent in the request body. Users should not be able to edit comments that they don’t own. An error 400 should be thrown if the user is not the owner. An error 404 should be thrown if the comment is not found.
  • DELETE /api/v1/comments/:id it is private and allows us to delete an existing comment by its ID. Users should not be able to delete comments that they don’t own. An error 400 should be thrown if the user is not the owner. An error 404 should be thrown if the comment is not found.


Security

(20%, 20 Marks)

You must implement user authentication using JWT tokens as we have explained during the lectures of this module. The endpoint flagged as "private" must be protected by a middleware that uses JWT tokens. Please implement the JWT tokens using the "jsonwebtoken" library.

You are going to need to define a middleware named "authMiddleware" in a file named "auth_middleware.ts" under the /src/middleware directory.


Web client

(10%, 10 Marks)

You must implement a web client for one each of the endpoints previously described.


Automated test

(20%, 20 Marks)

You must implement one integration test and one unit test:

The unit test must ensure that one of the POST methods in the links controller is correct. The controller must be tested in isolation. Please implement this test using the mocha and chai libraries.

We must ensure that one of the POST methods in the links controller is correct using an integration test. Please implement this test using the "supertest" library.