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.
Pre-requisites:
- VS Code
- NodeJS (recommended: nvm)
- yarn
- Insomnia
- Docker
- DBeaver
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.
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!'.
- 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
- Install
ESLint
plugin - Press Ctrl+Shift+P and
open settings (json)
, and then add:
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
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
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"
},
// ...
}
prettier.config.js
module.exports = {
singleQuote: true,
trailingComma: 'es5',
arrowParens: 'avoid',
}
- 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.
-
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;
- 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';
// ...
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.
[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
- 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).
-
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.
- 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'
)
- encrypt password (using
-
In the
users.routes
, make use of theCreateUserService
and create a new user. Note: do not give the password in the response.
- Install
yarn add jsonwebtoken
yarn add -D @types/jsonwebtoken
-
Create a
src/config/auth.ts
with the jwtsecret
andexpiresIn
. -
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 theroutes/index.ts
. -
Create the
AuthenticateUserService.ts
to check if the email and password are both valid. If yes, generate a token.
- Create
src/middlewares/ensureAuthenticated.ts
(suggestion for a better name:authenticationMiddleware
) to check if the token given in therequest.headers.authorization
is valid (remember to deal with theBearer:
).- Create the
src/@types/express.d.ts
:
- Create the
declare namespace Express {
export interface Request {
user: {
id: string;
};
}
}
- Make use of the
ensureAuthenticated
middleware in the/users/avatar
endpoint.
- 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 insrc/routes/users.routes.ts
- Use
multer.single()
middleware to get the file and save it in the server
- Use
-
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.
-
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 theUpdateUserAvatarService.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()
- fix the problem using
- Create the
src/routes/appointments.routes.ts
- Make it answer a 'Hello World'
- Use
appointments.routes
in theroutes/index
. - Test it with insomnia.
- 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.
- Create the
src/models/Appointments.ts
.- Add this to the Appointments model:
@ManyToOne(() => User)
@JoinColumn({ name: 'provider_id' })
provider: User
- Create
src/repositories/AppointmentsRepository.ts
- 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.