Appearance
Limites de Tamanho de Inputs (Canônico)
Fonte da verdade dos limites de caracteres aplicados em toda entrada de texto do produto (forms, search inputs, POSTs diretos). Frontend e backend espelham os mesmos valores.
Por que existe
Sem limite, qualquer usuário pode enviar payloads arbitrariamente grandes, abuso de recursos (DoS), explosão de logs, falhas em downstream, custos extras de storage/processamento. A defesa é em três camadas:
- Frontend (DOM): o atributo
maxLengthdo<input>/<textarea>bloqueia digitação além do limite. Fácil de burlar (DevTools, paste,el.value = ...). - Frontend (estado): a diretiva
v-max-lengthinterceptainputepasteevents, força truncamento e dispara o evento de input, garante que o v-model nunca contenha mais que o tier. Esta é a camada que enfrenta usuário malicioso que edita o atributo via DevTools. - Backend: rejeita payloads acima do limite via
@MaxLength+ ValidationPipe →400. Barreira final e única autoridade.
A diretiva v-max-length é obrigatória: :maxlength HTML sozinho é insuficiente.
Tiers canônicos
| Tier | Tamanho | Uso típico |
|---|---|---|
TINY | 50 | códigos OTP, slugs, CPF/CNPJ formatados, telefone, sigla |
SHORT | 120 | nome, email, título curto |
MEDIUM | 255 | subject, label, path, URL |
LONG | 1.000 | descrição curta, observação, notes |
XLONG | 5.000 | description completa, reason, comment de review, justifica |
HTML | 100.000 | htmlContent de template, body editável rico |
SEARCH | 120 | alias semântico para inputs de busca (igual a SHORT) |
Como escolher: pegue o tier mais próximo do uso real do campo. Se nenhum servir, NÃO crie tier inline, adicione um tier novo aqui e propague para os dois projetos.
Como adicionar novo tier: só justificável se houver 3+ campos reais precisando. Caso contrário, use o tier existente mais próximo (preferindo o maior).
Mapeamento canônico campo → tier
| Categoria de campo | Tier |
|---|---|
code, otp, cpf, cnpj, phone, slug | TINY |
name, email, tradeName, title, firstName, lastName | SHORT |
subject, url, path, documentNumber, address | MEDIUM |
notes, shortDescription, observation | LONG |
description, reason, comment, content, body, message, justification | XLONG |
htmlContent, templateBody, qualquer HTML rico | HTML |
search, q, query | SEARCH |
Onde vivem as constantes
| Projeto | Arquivo |
|---|---|
contrasync-nest-api | src/common/constants/text-length.constants.ts → exporta TEXT_LENGTH |
contrasync-vue-spa | src/domain/common/constants.ts → exporta TEXT_LENGTH |
Os valores DEVEM ser idênticos. Ao mudar este doc, atualize ambos os arquivos.
Como aplicar: Backend (Nest)
Em DTOs novos
Prefira o decorator composto @TextField:
ts
import { TextField } from '@common/decorators/text-field.decorator';
export class CreateContractDto {
@TextField({ tier: 'SHORT' })
name: string;
@TextField({ tier: 'XLONG', optional: true })
description?: string;
}Em DTOs existentes
Forma direta com class-validator:
ts
import { IsString, MaxLength } from 'class-validator';
import { TEXT_LENGTH } from '@common/constants/text-length.constants';
export class CreateContractDto {
@IsString()
@MaxLength(TEXT_LENGTH.SHORT)
name: string;
}Em DTOs de listagem
Herde PaginationQuery para receber search blindado:
ts
import { PaginationQuery } from '@common/types/pagination.type';
export class ListContractsDto extends PaginationQuery {
// search já vem com @MaxLength(TEXT_LENGTH.SEARCH)
}Como aplicar: Frontend (Vue SPA)
Em <n-input> / <n-textarea>: usar v-max-length (OBRIGATÓRIO)
A diretiva v-max-length é registrada globalmente em src/main.ts e definida em src/directives/maxLength.ts. Ela:
- Define o atributo
maxLengthno DOM (defesa rasa imediata) - Intercepta
inputepasteevents e trunca o valor reativamente - Re-dispara o evento de input para o v-model do Vue/Naive UI sincronizar
- Observa o slot do componente (n-input troca o
<input>interno em alguns casos) e re-anexa os listeners se necessário
vue
<template>
<n-input
v-model:value="form.name"
v-max-length="TEXT_LENGTH.SHORT"
:placeholder="t('placeholder.name')"
/>
</template>
<script setup lang="ts">
import { TEXT_LENGTH } from '@/domain/common/constants'
</script>Em search inputs de listagem
vue
<n-input
v-model:value="search"
v-max-length="TEXT_LENGTH.SEARCH"
:placeholder="t('search.placeholder')"
/>:maxlength HTML é proibido em código novo
vue
<!-- ❌ ERRADO atributo HTML pode ser editado via DevTools -->
<n-input :maxlength="TEXT_LENGTH.SHORT" />
<!-- ✅ CORRETO diretiva enforce no estado -->
<n-input v-max-length="TEXT_LENGTH.SHORT" />Em FormRules (validação de submit)
ts
const rules: FormRules = {
description: [
{ required: true, message: t('global.formErrorRequired') },
{ max: TEXT_LENGTH.XLONG, message: t('global.formErrorMaxLength', { max: TEXT_LENGTH.XLONG }) }
]
}Em editores ricos (htmlContent)
:maxlength não funciona em editores como Tiptap/Quill. Valide no hook antes do submit:
ts
const isHtmlValid = computed(() => htmlContent.value.length <= TEXT_LENGTH.HTML)
// Desabilita botão Salvar quando excederBody parser (Nest)
JSON global está limitado a 1mb em main.ts. Suficiente para qualquer DTO normal, payloads maiores devem usar upload (multipart/form-data via Multer, que tem limites próprios em upload-limits.constants.ts).
Se um caso de uso legítimo precisar de JSON > 1mb, considere reformular para upload em vez de subir o limite global.
Checklist obrigatório para novos forms/DTOs
- [ ] Backend: todo campo
@IsString()tem@MaxLength(TEXT_LENGTH.*)ou usa@TextField({ tier }) - [ ] Backend: DTOs de listagem herdam
PaginationQuery(ou justificam por que não) - [ ] Frontend: todo
<n-input>/<n-textarea>usav-max-length="TEXT_LENGTH.*"(NÃO:maxlength) - [ ] Frontend: editores ricos validam
.lengthno hook antes do submit - [ ] Tier escolhido está no mapeamento canônico desta página (ou novo tier foi adicionado aqui)
Referências cruzadas
- vue-spa/code-style.md: seção "Limites de Caracteres em Inputs"
- nest-api/code-style.md: seção "Limites de Caracteres em DTOs"