Skip to content
Aprendizados da semana (16 a 22)

Aprendizados da semana (16 a 22)

August 24, 2026

Semana do dia 16 a 22

  • responsabilidades: schema, types e dto
  • proteção de dados: unidirecional e bidirecional
  • ‘magic strings’ nas autorizações (como eu estava montando x como IA sugeriu - uma forma mais eficiente)

Responsabilidades: schema, types e dto (e o poder do zod no nestjs)

Eu já sabia de alguns detalhes, mas conforme fui desenvolvendo com o TS, fui enfrentando algumas dificuldades e entendendo melhor onde encaixar cada um deles

types

é responsável por definir a forma do dado, ótimo para o momento de desenvolvimento, ao apertar ponto e ver a sugestão das propriedades dentro daquele dado.

dto

Também define a forma do dado, mas com uma responsabilidade diferente: a transferência (Data Transfer Object) do objeto entre camadas da sua API.

schemas

São validações em tempo de execução (runtime), enquanto o seu sistema esta em produção. Eles basicamente dizem ’eu aceito apenas o campo email e senha, e eles precisam atender as seguintes regras’, veja um exemplo:

1
2
3
4
5
6
7
8
const email = z.string().email();
const password = z.string().min(8);


const loginLocal = z.object({
  email,
  password,
});

O poder do zod com nestjs

Além de cansativo ter que declarar uma forma para cada um desses itens, a probabilidade de gerar inconsistência conforme o tempo é alta. Então, uma forma de resolver o problema é declarando uma vez e aproveitando ela para outros usos. Veja o exemplo de ’loginLocal’:

Primeiro é feito a declaração do schema:

1
2
3
4
const loginLocal = z.object({
  email,
  password,
});

Depois é criado o tipo com a seguinte linha:

1
type loginLocalType = z.infer<typeof loginLocal>;

E, por fim, o DTO é feito da seguinte maneira:

1
2
3
4
import { createZodDto } from 'nestjs-zod';
import { loginLocal } from './user.schema';

export class LoginLocalDTO extends createZodDto(loginLocal) {}

momento de uso de cada um

Ao estar criando o DTO com base no schema, conforme exemplo anterior, “damos o poder” do DTO possuir as caracteristicas que o schema possui. Portanto, o primeiro local de uso seria na própria controller, em conjunto com um decorator para usar esses “poderes” herdados.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// controller
import { LocalUserDTO } from './schema e dto/user.dto';
import { ZodValidationPipe } from 'nestjs-zod';

@Post('/create/local')
@UsePipes(ZodValidationPipe)
public async registerLocal(@Body() data: LocalUserDTO) {
    return await this.usersService.registerLocalUser(data);
}


// service

const newUserEntity: NewLocalUserType = {
    person: data.personName,
    email: data.email,
    nickname: data.nickname,
    metadata,
    type: 'LOCAL',
    password: hashedPassword,
};

const entity = await this.userStorage.createLocalUser(newUserEntity);

proteção de dados: unidirecional e bidirecional - diferenças

Podemos comparar a proteção unidirecional como um triturador de papel. Após passar o papel na máquina, não há caminho de volta para restaurar o que foi triturado, é assim que as bibliotecas como argon2 e bcrypt funcionam, elas transformam a senha em um hash e depois não realizam o caminho de volta.

Já o bidirecional seria como um cofre, no qual você joga um conteúdo valioso e tranca o acesso a ele. É muito usado por uma lib de criação de tokens: JWT. Eles são codificados (geralmente, o id e/ou email), (via de ida) e quando necessário (validar que o usuário tem permissão para acessar X recurso) ele pode ser decodificado (via de volta), tornando o id/email em texto puro, digamos assim.

magic strings

Eu já ouvi falar em magic numbers, que são os números jogados em seu sistema que não revelam contexto sobre o que eles representam, exemplo:

1
2
3
4
5
if (status == 4)  // magic number, sem contexto


const MAX_RETRIES = 4
if (status == MAX_RETRIES)  // com contexto

Mas ao tentar implementar o sistema de autorização e pedir ajuda pra IA avaliar, ela me apresentou esse conceito de magic string. Antes eu estava criando um objeto contentendo todas as permissões que dado perfil possui:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22

export enum Role {
    USER_COMMON = 'user:common',
    ADMIN = 'admin'
}

export const ROLE_PERMISSIONS_MATRIX: Record<string, string[]> = {
    [Role.USER_COMMON]: [
        'post:create',
        'post:read',
        'post:update:own',
        'post:delete:own',
        'user:read',
        'user:update:own',
        'user:delete:own',
    ],
    [Role.ADMIN]: [
        'post:*',
        'user:*',
        'admin:*',
    ],
};

Um dos problemas é o de digitação e sempre precisar incrementar em cada perfil conforme novas permissões apareçam. Porém, me foi apresentado uma abordagem que achei bem mais interessante, em um arquivo ter todas as permissões por “feature”:

1
2
3
4
5
6
7
8
export const PostPermissions = {
    CREATE: 'post:create',
    READ: 'post:read',
    UPDATE_OWN: 'post:update:own',
    UPDATE_ANY: 'post:update:any',
    DELETE_OWN: 'post:delete:own',
    DELETE_ANY: 'post:delete:any',
} as const;

E no arquivo que atribui as permissões ao tipo de perfil, usar algo assim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19

import { PostPermissions } from './permissions';

const ALL_POST_PERMISSIONS = Object.values(PostPermissions);

export const ROLE_PERMISSIONS_MATRIX: Record<string, string[]> = {

    'user:common': [
        PostPermissions.CREATE,
        PostPermissions.READ,
        PostPermissions.UPDATE_OWN,
        PostPermissions.DELETE_OWN,
    ],

    'admin': [
        ...ALL_POST_PERMISSIONS,
    ],

}

Assim, garantindo que toda atribuição esteja mais explicita, se necessário a criação de uma nova, criar a variável e atribuir aos perfis cabiveis. Alem de ajudar no autocomplete dentro de um guard nas controllers, evitando erros de digitação (o que já aconteceu muitas vezes comigo).