Skip to content

Latest commit

 

History

History
532 lines (404 loc) · 11.3 KB

02.99-gobarber-backend-summary.md

File metadata and controls

532 lines (404 loc) · 11.3 KB

GoBarber Backend Summary

The goal here is to quickly achieve the final code of this module of the course but in a shorter path than the one taken during the course.

Development Environment

Pre-requisites:

  • VS Code
  • NodeJS (recommended: nvm)
  • yarn
  • Insomnia
  • Docker
  • DBeaver

GitHub Repository

I recommend starting with a github repository so we can get a commit history of our project. So, go to github and create a new repo.

Kickstarting the Code

Clone your repo and start coding!

git clone https://github.com/{USERNAME}/gobarber
code gobarber

tip: Generate a .gitignore with https://gitignore.io: vim,node,vscode,linux,macos.

Once in the project's directory, install the initial packages:

yarn init -y
yarn add express
yarn add -D typescript @types/express ts-node-dev
yarn tsc --init # generates the tsconfig.json
  • Add this options to tsconfig.json:
{
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "strictPropertyInitialization": false,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
  }
}
  • Add this entry to package.json:
{
  "scripts": {
    "build": "tsc",
    "dev:server": "ts-node-dev --transpile-only --ignore-watch node_modules src/server.ts"
  },
}
  • Create the src/server.ts answering with a 'Hello World!'.

EditorConfig

  • Install EditorConfig for VS Code plugin
  • right-click on the file structure and choose Generate .editorconfig
  • put this content in the .editorconfig file:
root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf

ESLint

  • Install ESLint plugin
  • Press Ctrl+Shift+P and open settings (json), and then add:
"editor.codeActionsOnSave": {
  "source.fixAll.eslint": true
}

Node

For node, let's use the 6.8.0 version, as the new ones have some inconveniences.

[TODO: what are the inconveniences in the new version of eslint?]

yarn -D add eslint@6.8.0
yarn eslint --init
# 1. To check syntax, find problems and enforce code style
# 2. JavaScript modules (import/export)
# 3. None of these
# 4. (use TypeScript?) Yes
# 5. (mark only Node with space bar and then Enter)
# 6. Use a popular style guide
# 7. Airbnb
# 8. JSON
# 9. (install with npm?) No
# copy the packages shown on the question 9 above and then...
yarn add -D @typescript-eslint/eslint-plugin@latest eslint-config-airbnb-base@latest eslint-plugin-import@^2.21.2 @typescript-eslint/parser@latest
yarn add -D eslint-import-resolver-typescript

Create the .eslintignore file:

/*.js
node_modules
dist

Edit .eslintrc.json:

{
  // ...
  "extends": [
    // ...
    "plugin:@typescript-eslint/recommended"
  ],
  // ...
  "rules": {
    "class-methods-use-this": "off",
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "ts": "never"
      }
    ]
  },
  // ...
  "settings": {
    "import/resolver": {
      "typescript": {}
    }
  }
  // ...
}

Restart VS Code

Prettier

yarn add -D prettier eslint-config-prettier eslint-plugin-prettier

Edit the .eslintrc.json:

{
  // ...
  "extends": [
    "prettier/@typescript-eslint",
    "plugin:prettier/recommended",
  ],
  // ...
  "plugins": [
    // ...
    "prettier"
  ],
  "rules": {
    // ...
    "prettier/prettier": "error"
  },
  // ...
}

Solving conflicts between ESLint and Prettier.

prettier.config.js

module.exports = {
  singleQuote: true,
  trailingComma: 'es5',
  arrowParens: 'avoid',
}

Debugging in VS Code

  • Click on the VS Code debugging icon.
  • Click create a launch.json file.

.vscode/launch.json:

{
  "version": "...",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "protocol": "inspector",
      "restart": true,
      "name": "Debug",
      "skipFiles": [
        "<node_internals>/**"
      ],
      "outFiles": [
        "${workspaceFolder}/**/*.js"
      ]
    }
  ]
}

package.json:

  "dev:server": "ts-node-dev --inspect --transpile-only --ignore node_modules src/server.ts"

Now when launching dev:server the debugger will be listening and if you click in the debuggin icon it'll be attached to the application's debugger.

Use the WATCH to add variables you want to check.

Note: when the debugger is attached, the bottom bar becomes red.

Docker & Data Base

  • Install docker following the instructions in https://docs.docker.com/install/ and don’t forget to follow the post-install instructions.

  • Installing a PostgreSQL container:

docker run --name gostack_postgresql -e POSTGRES_PASSWORD=docker -p 5432:5432 -d postgres
#       --name=""
#          Assign a name to the container
#       -e, --env=[]
#          Set environment variables
#       -p, --publish ip:[hostPort]:containerPort | [hostPort:]containerPort
#          Publish a container's port, or range of ports, to the host.
#       -d, --detach=true|false
#          Detached mode: run the container in the background and print the new container ID. The default is false.
  • Install DBeaver: https://dbeaver.io/

  • Through DBeaver, create a database named gostack_gobarber. Via SQL it would be like this:

create database gobarber_project;

TypeORM

  • Install:
yarn add typeorm pg reflect-metadata
  • Add typeorm to the package.json:
"scripts": {
  "typeorm": "ts-node-dev ./node_modules/typeorm/cli.js"
}
  • ormconfig.json:
{
  "type": "postgres",
  "host": "localhost", // host of your database
  "port": 5432,
  "username": "postgres",
  "password": "docker",
  "database": "gostack_gobarber",
  "entities": [
    "./src/models/*.ts"
  ],
  "migrations": [
    "./src/database/migrations/*.ts"
  ],
  "cli": {
    "migrationsDir": "./src/database/migrations"
  }
}
  • src/database/index.ts to connect to the database:
import { createConnection } from 'typeorm';

createConnection();
  • src/server.ts:
import 'reflect-metadata';
// ...
import './database';
// ...

Users

API Endpoints

Before actually implementing the CRUD for the users, create some requests inside insomnia:

  • GET /users/ [authenticated] - returns the individual user's data
  • POST /users - body: name, email, password
  • PUT /users/:id [authenticated] - body: name?, email?, password?
  • PATCH /users/avatar [authenticated] - file: avatar image
  • POST /sessions - body: email, password

Create the src/routes/users.routes.ts with a dummy JSON response for each one of those endpoints.

Import user.routes in routes/index and use it.

Test the endpoints with insomnia.

Registering Users

[note: this seems to make the migration step easier: TypeORM Migrations Explained]

  • migration
yarn typeorm migration:create -n CreateUsers
  • Columns to be created

    • id
    • name
    • email
    • password
    • avatar
    • created_at
    • updated_at
  • run the migration:

yarn typeorm migration:run
  • Check in the DBeaver if the table was really created.

IMPORTANT: if while running a migration you receive this error:

QueryFailedError: function uuid_generate_v4() does not exist

Then run this command inside PostgreSQL, in the gostack_gobarber database:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

  • Create the src/models/User.ts.

  • Create the src/services/CreateUserService and handle these business rules before persisting data:

    • password, name, email not empty
    • email is valid
    • password.length > 6
    • etc.
  • Test with insomnia giving invalid input (it'll break the app when throwing errors).

Handling Errors

  • Create the src/errors/AppError.ts.

  • Add the global exception handler in the src/server.ts.

  • Test the exception handler by trying to creat an user with invalid input.

Password Encryption

  • Install bcrypt in order to encrypt user's password before persisting it:
yarn add bcryptjs
yarn add -D @types/bcryptjs
  • In the src/services/CreateUserService and handle these business rules before persisting data:

    • encrypt password (using { hash } from 'bcryptjs')
  • In the users.routes, make use of the CreateUserService and create a new user. Note: do not give the password in the response.

Authentication

Requesting a Token

  • Install
yarn add jsonwebtoken
yarn add -D @types/jsonwebtoken
  • Create a src/config/auth.ts with the jwt secret and expiresIn.

  • Create the sessions.routes.ts to receive the email and password, pass them to the service and give the token as response.

  • Import the sessions.routes in the routes/index.ts.

  • Create the AuthenticateUserService.ts to check if the email and password are both valid. If yes, generate a token.

Authentication Middleware

  • Create src/middlewares/ensureAuthenticated.ts (suggestion for a better name: authenticationMiddleware) to check if the token given in the request.headers.authorization is valid (remember to deal with the Bearer: ).
    • Create the src/@types/express.d.ts:
declare namespace Express {
  export interface Request {
    user: {
      id: string;
    };
  }
}
  • Make use of the ensureAuthenticated middleware in the /users/avatar endpoint.

Updating The Avatar

Upload The File

  • Install:
yarn add multer
yarn add -D @types/multer
  • Create src/config/upload.ts to config some stuff for multer.

  • Create a PATCH /users/avatar route in src/routes/users.routes.ts

    • Use multer.single() middleware to get the file and save it in the server
  • Test it with insomnia

    • invalid token
    • valid token and valid file
  • Edit the src/server.ts to serve the image files statically.

  • Test in a browser.

Updating the avatar field

  • Create the src/services/UpdateUserAvatarService.ts:

    • get the user data from the db (throw error if not found)
    • if user's avatar is not null, delete the old file
    • set the path to the new file as user.avatar
  • Edit the src/routes/users.routes.ts in order to use the UpdateUserAvatarService.ts.

  • Test with insomnia.

    • no file
    • valid token and valid file
    • delete the file in /tmp dir and try to send a new one
      • fix the problem using fs.existsSync()

Appointments

  • Create the src/routes/appointments.routes.ts
    • Make it answer a 'Hello World'
    • Use appointments.routes in the routes/index.
    • Test it with insomnia.

Appointments Table

  • migration
yarn typeorm migration:create -n CreateAppointments
  • Columns to be created

    • id
    • provider_id
    • date
    • created_at
    • updated_at
  • run the migration:

yarn typeorm migration:run
  • Check in the DBeaver if the table was really created.

Appointments Model

  • Create the src/models/Appointments.ts.
    • Add this to the Appointments model:
  @ManyToOne(() => User)
  @JoinColumn({ name: 'provider_id' })
  provider: User

Appointments Repository

  • Create src/repositories/AppointmentsRepository.ts

CreateAppointmentService

  • Install:
yarn add date-fns
  • Create the src/services/CreateAppointmentService.ts

  • Call the service in the appointments.routes.

  • Test with insomnia

    • Create some appointments
    • List all appointments
    • Turn off the server and then test again to check if the previously created appointments persisted.