Skip to content

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:

  1. Frontend (DOM): o atributo maxLength do <input>/<textarea> bloqueia digitação além do limite. Fácil de burlar (DevTools, paste, el.value = ...).
  2. Frontend (estado): a diretiva v-max-length intercepta input e paste events, 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.
  3. 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

TierTamanhoUso típico
TINY50códigos OTP, slugs, CPF/CNPJ formatados, telefone, sigla
SHORT120nome, email, título curto
MEDIUM255subject, label, path, URL
LONG1.000descrição curta, observação, notes
XLONG5.000description completa, reason, comment de review, justifica
HTML100.000htmlContent de template, body editável rico
SEARCH120alias 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 campoTier
code, otp, cpf, cnpj, phone, slugTINY
name, email, tradeName, title, firstName, lastNameSHORT
subject, url, path, documentNumber, addressMEDIUM
notes, shortDescription, observationLONG
description, reason, comment, content, body, message, justificationXLONG
htmlContent, templateBody, qualquer HTML ricoHTML
search, q, querySEARCH

Onde vivem as constantes

ProjetoArquivo
contrasync-nest-apisrc/common/constants/text-length.constants.ts → exporta TEXT_LENGTH
contrasync-vue-spasrc/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 maxLength no DOM (defesa rasa imediata)
  • Intercepta input e paste events 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 exceder

Body 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> usa v-max-length="TEXT_LENGTH.*" (NÃO :maxlength)
  • [ ] Frontend: editores ricos validam .length no hook antes do submit
  • [ ] Tier escolhido está no mapeamento canônico desta página (ou novo tier foi adicionado aqui)

Referências cruzadas