Skip to content

Latest commit

 

History

History
267 lines (213 loc) · 8.05 KB

02.2-backend-registering-users.md

File metadata and controls

267 lines (213 loc) · 8.05 KB

Backend - Registering Users

User's Model and Migration

yarn typeorm migration:create -n CreateUsers
  • Columns in a minimalistic users table:
    • id
    • name
    • email
    • password
    • created_at
    • updated_at

Edit the src/database/migrations/*CreateUsers.ts:

import { MigrationInterface, QueryRunner, Table } from 'typeorm';

export default class CreateUsers1601474273498 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.createTable(
      new Table({
        name: 'users',
        columns: [
          {
            name: 'id',
            type: 'varchar', // later it'll be changed to uuid
            isPrimary: true,
            generationStrategy: 'uuid',
            default: 'uuid_generate_v4()',
          },
          {
            name: 'name',
            type: 'varchar',
          },
          {
            name: 'email',
            type: 'varchar',
            isUnique: true,
          },
          {
            name: 'password',
            type: 'varchar',
          },
          {
            name: 'created_at',
            type: 'timestamp',
            default: 'now()',
          },
          {
            name: 'updated_at',
            type: 'timestamp',
            default: 'now()',
          },
        ],
      }),
    );
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.dropTable('users');
  }
}

And then:

yarn typeorm migration:run

Create the src/models/User.ts:

import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';

@Entity('users')
class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  name: string;

  @Column()
  email: string;

  @Column()
  password: string;

  @CreateDateColumn()
  created_at: Date;

  @UpdatedDateColumn()
  updated_at: Date;
}

Note: it's quite common to have the fields created_at and updated_at in every database table.

Add the fields created_at and updated_at to the appointments migration/model. You'll need to:

  1. Revert the migration(s)
  2. Edit the appointment's migration
  3. Run the migrations again
  4. Edit the appointment's model accordingly
  5. (check how the database is filled)

Relations in the Models

Note: during the video, around 7min, it's possible to see how TypeScript+VSCode can make coding faster.

Creating a relationship between the appointments and the users table (as the provider in fact is an user).

  1. In the src/models/Appointments.ts, replace the column provider with provider_id.

  2. In the *CreateUsers and *CreateAppointments migrations, change the id column's type from varchar to uuid.

  3. Revert the migrations and then run them again.

  4. Create a migration to change the appointments table:

yarn typeorm migration:create -n AppointmentsProviderIsAnUser

4.1. drop the column provider 4.2. add the column provider_id 4.3. create a foreign key referencing users.id

Note 1: in the appointments table, the provider_id is nullable, in order to allow removing provider's info from the database without messing with client's appointments log. Also onDelete: 'SET NULL' to achieve this goal.

Note 2: in a migration that alters a table, the down() method MUST be changed in the specific reverse order of the sequence of changes being done in the up() method.

  1. Run the migrations

  2. INTERESTING!: DBeaver has a feature to show the ER Diagrams! Seems to be quite handy!

  1. IMPORTANT: the Appointments model can have a property, which is NOT a column in the appointments table, to provide an easy access to the User bound to the provider_id:
// ...
import User from './User';
// ...
class Appointment {
  // ...
  @ManyToOne(() => User)
  @JoinColumn({ name: 'provider_id' })
  provider: User;
  // ...
}
  1. Test with insomnia (at least to read some error messages).

Route to Create Users

  1. create src/routes/users.routes.ts.
  2. import the users route in the src/routes/index.ts.
  3. create the src/services/CreateUserService handling the business rules.
  • Note: There's no need to write a repository code if the model has only the default CRUD operations. Just use the getRepository() method (from typeorm) using the model as the argument and you'll have access to the repositoriy's methods.
  1. adapt the appointments.routes.ts and the CreateAppointmentService.ts to use provider_id rather than provider.
  • if needed, disable eslint camelCase.
  1. test with insomnia.

Encrypting the Password

yarn add bcryptjs
yarn add -D @types/bcryptjs

src/services/CreateUserService.ts:

import { hash } from 'bcryptjs'
// ...
  public async execute() { // ...
    // ...
    const hashedPassword = await hash(password, 8); // magic number?!

    const user = usersRepository.create({
      name,
      email,
      password: hashedPassword,
    });
    // ...
  }

src/routes/users.routes.ts:

  // ...
  // destructuring but ignoring the hashed password
  const { id, name, email, created_at, updated_at } = await createUser.execute({
    name,
    email,
    password
  });

  const user = {
    id,
    name,
    email,
    created_at,
    updated_at
  }

  return response.json(user);
  // ...

Summary

Files that need to be created/coded in order to add Users to the project.

  • Users migration.
    • Columns in a minimalistic users table:
      • id
      • name
      • email
      • password
      • created_at
      • updated_at
  • User's model.
  • UserRepository (in this case it won't be necessary, as we're using only methods already available in the TypeORM's repository implementation).
  • CreateUserService to create a new user in the repository.
    • some business rules:
      • email must be unique
      • email must a valid email address
      • password cannot be empty
      • password must be hashed (hash() method from bcryptjs)
  • Route to create a new user
    • Note: the response must not answer with the password.
  • Create the relation between appointments and users (a provider is a user)
    • In the Appointments model, replace provider with provider_id.
    • Create an AppointmentsProviderIsAnUser migration.
      • drop column provider
      • add column provider_id
      • create a foreign key referencing provider_id to users.id
      • the down() method must undo that 👆 in the reverse order.
    • Again in the Appointments' model, add a property provider to allow an easy access to the provider's info (@ManyToOne, @JoinColumn, etc.).

Need More Understanding

I didn't really understood those decorators used in the models. I need to research more about that later.

My GoBarber codebase up to this point

https://github.com/meleu/gobarber/tree/184ad075b0c624f7020da01da96286690549a39a