Para: Desenvolvedor Júnior Iniciante
Objetivo: Criar um endpoint REST para cadastrar faturas
Tempo estimado: 3-4 horas
Nível de dificuldade: ⭐⭐⭐ Intermediário
📋 Pré-requisitos
Antes de começar, certifique-se de que você completou:
- ✅ Projeto
invoice-servicecriado e funcionando - ✅ IntelliJ IDEA configurado corretamente
- ✅ Aplicação roda em
http://localhost:8080 - ✅ Estrutura de pacotes criada (
controller,service,model,repository)
Se algo estiver pendente, volte ao guia Como criar um novo projeto Spring Boot! 😊
🎯 O que vamos construir?
Hoje você vai criar sua primeira API REST funcional! Especificamente:
- Modelo de dados Invoice - A estrutura que representa uma fatura
- Endpoint POST /invoices - Para criar novas faturas
- Validações - Garantir que os dados estão corretos
- Testes - Verificar se tudo funciona usando Postman ou cURL
Resultado final:
# Enviar requisição
POST http://localhost:8080/invoices
{
"customerId": "CUST001",
"amount": 150.50,
"status": "PENDING"
}
# Receber resposta 201 Created
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"customerId": "CUST001",
"amount": 150.50,
"status": "PENDING",
"createdAt": "2025-12-29T18:00:00"
}
Legal, né? Vamos lá! 🚀
📦 Parte 1: Criar o Modelo de Dados (Invoice)
O que é um modelo de dados?
É uma classe que representa um objeto do mundo real. No nosso caso, uma fatura (invoice).
Analogia: Pense em um formulário de papel. O modelo é como o template desse formulário, definindo quais campos existem (nome, CPF, valor, etc.).
Passo a Passo
1.1 Criar a Data Class Invoice
- No IntelliJ, navegue até:
src/main/kotlin/com/invoice/model/ - Clique com botão direito na pasta
model→ New → Kotlin Class/File - Digite:
Invoice - Selecione: Class
1.2 Implementar a Data Class
Abra o arquivo Invoice.kt e adicione o seguinte código:
package com.invoice.model
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Positive
import java.math.BigDecimal
import java.time.LocalDateTime
import java.util.UUID
data class Invoice(
val id: String = UUID.randomUUID().toString(),
@field:NotBlank(message = "Customer ID é obrigatório")
val customerId: String,
@field:NotNull(message = "Valor é obrigatório")
@field:Positive(message = "Valor deve ser positivo")
val amount: BigDecimal,
@field:NotBlank(message = "Status é obrigatório")
val status: String,
val createdAt: LocalDateTime = LocalDateTime.now()
)
1.3 Entendendo o Código Linha por Linha
Vamos explicar cada parte:
1. Imports:
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Positive
Esses imports trazem as anotações de validação. São como "regras" que garantem dados válidos.
2. Data Class:
data class Invoice(...)
-
data class- Tipo especial do Kotlin que gera automaticamente:- Método
equals()- compara objetos - Método
hashCode()- útil para coleções - Método
toString()- mostra os valores - Método
copy()- cria cópias com alterações
- Método
Analogia: É como um atalho que escreve muito código por você!
3. Propriedades:
val id: String = UUID.randomUUID().toString()
-
val- Propriedade imutável (não pode ser alterada depois) -
id: String- Nome e tipo da propriedade -
UUID.randomUUID().toString()- Gera um ID único automaticamente- Exemplo:
"550e8400-e29b-41d4-a716-446655440000"
- Exemplo:
@field:NotBlank(message = "Customer ID é obrigatório")
val customerId: String
-
@field:NotBlank- Validação: não pode ser vazio ou só espaços -
message = "..."- Mensagem de erro customizada -
customerId: String- ID do cliente
@field:NotNull(message = "Valor é obrigatório")
@field:Positive(message = "Valor deve ser positivo")
val amount: BigDecimal
-
BigDecimal- Tipo para valores monetários (mais preciso queDouble) -
@NotNull- Não pode ser nulo -
@Positive- Deve ser maior que zero
Por que BigDecimal? Números decimais como Double têm problemas de arredondamento. Para dinheiro, use sempre BigDecimal!
val createdAt: LocalDateTime = LocalDateTime.now()
-
LocalDateTime- Data e hora sem timezone -
LocalDateTime.now()- Pega data/hora atual automaticamente
1.4 Adicionar Dependência de Validação
Para as validações funcionarem, precisamos adicionar a dependência no build.gradle.kts.
- Abra o arquivo
build.gradle.kts - Localize a seção
dependencies - Adicione esta linha:
dependencies {
// ... dependências existentes ...
// Validação
implementation("org.springframework.boot:spring-boot-starter-validation")
}
- Clique no ícone 🐘 (elefante do Gradle) que aparece no canto superior direito
- Selecione Reload Gradle Project
⏱️ Aguarde alguns segundos enquanto o Gradle baixa a dependência.
1.5 Testar a Data Class
Vamos verificar se a classe compila corretamente:
- Clique com botão direito no arquivo
Invoice.kt - Selecione Build Module 'invoice-service.main'
✅ Se não aparecer erro, perfeito! Sua data class está pronta.
🎮 Parte 2: Criar o Controller
O que é um Controller?
É a classe que recebe requisições HTTP e as processa. É a "porta de entrada" da sua API.
Analogia: É como o atendente de um restaurante que anota seu pedido e leva para a cozinha.
Passo a Passo
2.1 Criar a Classe InvoiceController
- Navegue até:
src/main/kotlin/com/invoice/controller/ - Clique com botão direito → New → Kotlin Class/File
- Digite:
InvoiceController - Selecione: Class
2.2 Implementar o Controller Básico
Adicione o seguinte código:
package com.invoice.controller
import com.invoice.model.Invoice
import jakarta.validation.Valid
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/invoices")
class InvoiceController {
// Lista temporária para armazenar faturas (in-memory)
private val invoices = mutableListOf<Invoice>()
@PostMapping
fun createInvoice(@Valid @RequestBody invoice: Invoice): ResponseEntity<Invoice> {
// Adiciona a fatura na lista
invoices.add(invoice)
// Retorna 201 Created com a fatura criada
return ResponseEntity.status(HttpStatus.CREATED).body(invoice)
}
}
2.3 Entendendo o Código Linha por Linha
1. Anotações da Classe:
@RestController
@RequestMapping("/invoices")
class InvoiceController
-
@RestController- Diz ao Spring: "Esta classe é um controller REST"- Automaticamente converte objetos para JSON
- Processa requisições HTTP
-
@RequestMapping("/invoices")- Define o caminho base- Todas as rotas deste controller começam com
/invoices
- Todas as rotas deste controller começam com
2. Armazenamento Temporário:
private val invoices = mutableListOf<Invoice>()
-
mutableListOf<Invoice>()- Lista mutável (pode adicionar/remover) - Por enquanto, armazena só na memória (quando reiniciar a app, perde tudo)
- Na quarta-feira vamos adicionar banco de dados real!
3. Método createInvoice:
@PostMapping
fun createInvoice(@Valid @RequestBody invoice: Invoice): ResponseEntity<Invoice>
Quebra completa:
-
@PostMapping- Esta função responde a requisições HTTP POST- Caminho completo:
POST /invoices(lembra do@RequestMapping?)
- Caminho completo:
fun createInvoice- Nome do método (pode ser qualquer nome)-
@Valid- Valida o objeto usando as anotações que definimos (@notblank, etc.)- Se validação falhar, retorna 400 Bad Request automaticamente
@RequestBody- O Spring pega o JSON da requisição e converte para objeto Invoiceinvoice: Invoice- Parâmetro que recebe os dados-
: ResponseEntity<Invoice>- Tipo de retorno-
ResponseEntitypermite controlar status HTTP (201, 200, 404, etc.) -
<Invoice>- O corpo da resposta será um objeto Invoice
-
4. Lógica do Método:
invoices.add(invoice)
- Adiciona a fatura recebida na lista
return ResponseEntity.status(HttpStatus.CREATED).body(invoice)
-
ResponseEntity.status(HttpStatus.CREATED)- Define status 201 (Created) -
.body(invoice)- Coloca a fatura no corpo da resposta - Spring automaticamente converte para JSON
Por que 201 e não 200?
- 200 OK: Requisição bem-sucedida (usado em GET, PUT)
- 201 Created: Recurso criado com sucesso (usado em POST)
É uma boa prática usar o status HTTP correto!
🧪 Parte 3: Testar o Endpoint
Opção A: Testar com cURL (Terminal)
O cURL é uma ferramenta de linha de comando para fazer requisições HTTP.
3.1 Iniciar a Aplicação
- No IntelliJ, abra
InvoiceServiceApplication.kt - Clique no ▶️ verde ao lado de
fun main - Aguarde aparecer:
Started InvoiceServiceApplication
3.2 Fazer a Requisição
Abra um novo terminal (fora do IntelliJ) e execute:
curl -X POST http://localhost:8080/invoices \
-H "Content-Type: application/json" \
-d '{
"customerId": "CUST001",
"amount": 150.50,
"status": "PENDING"
}'
Explicação do comando:
-
curl- Ferramenta para fazer requisições HTTP -
-X POST- Método HTTP POST -
http://localhost:8080/invoices- URL do endpoint -
-H "Content-Type: application/json"- Header dizendo que enviamos JSON -
-d '{...}'- Corpo da requisição (dados em JSON)
Resposta esperada:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"customerId": "CUST001",
"amount": 150.50,
"status": "PENDING",
"createdAt": "2025-12-29T18:00:00"
}
✅ Se você viu isso, PARABÉNS! Seu endpoint está funcionando! 🎉
Opção B: Testar com Postman (Recomendado)
Postman é uma ferramenta visual mais amigável para testar APIs.
3.3 Instalar Postman
- Acesse: https://www.postman.com/downloads/
- Baixe e instale
- Crie uma conta gratuita (ou pule)
3.4 Criar a Requisição
- Abra o Postman
- Clique em New → HTTP Request
- Configure:
| Campo | Valor |
|---|---|
| Método | POST |
| URL | http://localhost:8080/invoices |
- Vá na aba Body
- Selecione raw
- No dropdown ao lado, selecione JSON
- Cole este JSON:
{
"customerId": "CUST001",
"amount": 150.50,
"status": "PENDING"
}
- Clique em Send
Resultado esperado:
- Status:
201 Created(canto superior direito) - Body: JSON com a fatura criada, incluindo
idecreatedAt
3.5 Testar Validações
Vamos ver se as validações estão funcionando:
Teste 1: Enviar valor negativo
{
"customerId": "CUST001",
"amount": -50.00,
"status": "PENDING"
}
Resultado esperado: 400 Bad Request com mensagem "Valor deve ser positivo"
Teste 2: Enviar customerId vazio
{
"customerId": "",
"amount": 100.00,
"status": "PENDING"
}
Resultado esperado: 400 Bad Request com mensagem "Customer ID é obrigatório"
Teste 3: Não enviar amount
{
"customerId": "CUST001",
"status": "PENDING"
}
Resultado esperado: 400 Bad Request com mensagem "Valor é obrigatório"
✅ Se todos os testes passaram, suas validações estão funcionando!
📊 Parte 4: Adicionar Logs para Debug
Logs ajudam a entender o que está acontecendo na aplicação.
4.1 Adicionar Logger no Controller
Atualize seu InvoiceController.kt:
package com.invoice.controller
import com.invoice.model.Invoice
import jakarta.validation.Valid
import org.slf4j.LoggerFactory
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/invoices")
class InvoiceController {
private val logger = LoggerFactory.getLogger(InvoiceController::class.java)
private val invoices = mutableListOf<Invoice>()
@PostMapping
fun createInvoice(@Valid @RequestBody invoice: Invoice): ResponseEntity<Invoice> {
logger.info("Recebida requisição para criar fatura: {}", invoice)
invoices.add(invoice)
logger.info("Fatura criada com sucesso. ID: {}", invoice.id)
return ResponseEntity.status(HttpStatus.CREATED).body(invoice)
}
}
O que mudou:
-
private val logger = ...- Cria um objeto logger -
logger.info("...")- Escreve mensagens no console
4.2 Testar os Logs
- Reinicie a aplicação
- Faça uma requisição POST
- Observe o console do IntelliJ
Você verá algo como:
INFO c.i.controller.InvoiceController - Recebida requisição para criar fatura: Invoice(id=550e8400..., customerId=CUST001, amount=150.50, status=PENDING, createdAt=2025-12-29T18:00:00)
INFO c.i.controller.InvoiceController - Fatura criada com sucesso. ID: 550e8400-e29b-41d4-a716-446655440000
🎨 Parte 5: Melhorias Opcionais (Bônus)
5.1 Criar um Enum para Status
Ao invés de aceitar qualquer texto no status, vamos limitar aos valores válidos.
Crie o arquivo: src/main/kotlin/com/invoice/model/InvoiceStatus.kt
package com.invoice.model
enum class InvoiceStatus {
PENDING,
PAID,
CANCELLED,
OVERDUE
}
Atualize a classe Invoice:
data class Invoice(
val id: String = UUID.randomUUID().toString(),
@field:NotBlank(message = "Customer ID é obrigatório")
val customerId: String,
@field:NotNull(message = "Valor é obrigatório")
@field:Positive(message = "Valor deve ser positivo")
val amount: BigDecimal,
@field:NotNull(message = "Status é obrigatório")
val status: InvoiceStatus, // Mudou aqui!
val createdAt: LocalDateTime = LocalDateTime.now()
)
Agora o JSON deve ser:
{
"customerId": "CUST001",
"amount": 150.50,
"status": "PENDING"
}
Se enviar status inválido (ex: "INVALID"), retorna erro automaticamente!
5.2 Adicionar Endpoint GET para Listar Faturas
Vamos adicionar um endpoint para ver todas as faturas criadas:
@GetMapping
fun getAllInvoices(): ResponseEntity<List<Invoice>> {
logger.info("Recebida requisição para listar todas as faturas")
return ResponseEntity.ok(invoices)
}
Testar:
curl http://localhost:8080/invoices
Ou no Postman: GET http://localhost:8080/invoices
✅ Critérios de Aceite - Checklist Final
Marque cada item que você completou:
Criar Modelo Invoice
- [ ] Arquivo
Invoice.ktcriado emmodel/ - [ ] Data class com 5 campos: id, customerId, amount, status, createdAt
- [ ] Validações adicionadas: @notblank, @NotNull, @positive
- [ ] Dependência
spring-boot-starter-validationadicionada no build.gradle.kts - [ ] Classe compila sem erros
Criar InvoiceController
- [ ] Arquivo
InvoiceController.ktcriado emcontroller/ - [ ] Classe anotada com @RestController
- [ ] @RequestMapping("/invoices") configurado
- [ ] Lista in-memory criada (mutableListOf)
- [ ] Método createInvoice implementado
- [ ] Método anotado com @PostMapping
- [ ] Parâmetro com @valid e @RequestBody
- [ ] Retorna ResponseEntity com status 201
Testar Endpoint
- [ ] Aplicação inicia sem erros
- [ ] Requisição POST retorna 201 Created
- [ ] Resposta contém id gerado automaticamente
- [ ] Resposta contém createdAt gerado automaticamente
- [ ] Validações funcionam (testes com dados inválidos retornam 400)
- [ ] Logs aparecem no console
Bônus (Opcional)
- [ ] Enum InvoiceStatus criado
- [ ] Status aceita apenas valores do enum
- [ ] Endpoint GET /invoices implementado
- [ ] GET retorna lista de faturas criadas
🐛 Problemas Comuns e Soluções
Erro: "Unresolved reference: NotBlank"
Causa: Dependência de validação não foi adicionada
Solução:
- Verifique se adicionou
spring-boot-starter-validationnobuild.gradle.kts - Clique no ícone Gradle para recarregar as dependências
- Aguarde o download completar
Erro: 404 Not Found ao fazer POST
Causa: Aplicação não está rodando ou URL incorreta
Solução:
- Verifique se a aplicação está rodando (console deve mostrar "Started InvoiceServiceApplication")
- Confirme a URL:
http://localhost:8080/invoices(sem/api) - Verifique o método: deve ser POST, não GET
Erro: 400 Bad Request sem mensagem clara
Causa: JSON malformado
Solução:
- Use um validador JSON online: jsonlint.com
- Verifique vírgulas, aspas e chaves
- Exemplo correto:
{
"customerId": "CUST001",
"amount": 150.50,
"status": "PENDING"
}
Erro: "HttpMessageNotReadableException"
Causa: Tipo de dado incorreto no JSON
Solução:
-
amountdeve ser número:150.50(sem aspas) -
customerIdestatusdevem ser texto:"CUST001"(com aspas)
Validações não funcionam (aceita dados inválidos)
Causa: Esqueceu @Valid no parâmetro
Solução:
Verifique se o método tem:
fun createInvoice(@Valid @RequestBody invoice: Invoice)
^^^^^^
Resposta retorna 200 ao invés de 201
Causa: Esqueceu de definir o status HTTP
Solução:
Retorne com:
return ResponseEntity.status(HttpStatus.CREATED).body(invoice)
Não use apenas:
return ResponseEntity.ok(invoice) // Retorna 200
🎓 Conceitos Importantes para Entender
O que é REST?
REST (Representational State Transfer) é um estilo de arquitetura para APIs web.
Princípios básicos:
- Recursos são identificados por URLs (
/invoices) - Usam métodos HTTP padrão (GET, POST, PUT, DELETE)
- São stateless (cada requisição é independente)
- Respostas geralmente em JSON
Analogia: É como organizar uma biblioteca:
- Cada livro tem uma identificação única (URL)
- Ações padronizadas: pegar (GET), adicionar (POST), atualizar (PUT), remover (DELETE)
O que é JSON?
JSON (JavaScript Object Notation) é um formato de texto para trocar dados.
Exemplo:
{
"nome": "João",
"idade": 25,
"ativo": true
}
Por que usamos JSON?
- Fácil de ler para humanos
- Fácil de processar para computadores
- Padrão da indústria para APIs
HTTP Status Codes Importantes
| Código | Nome | Quando usar |
|---|---|---|
| 200 | OK | Requisição bem-sucedida (GET, PUT) |
| 201 | Created | Recurso criado com sucesso (POST) |
| 400 | Bad Request | Dados inválidos enviados |
| 404 | Not Found | Recurso não encontrado |
| 500 | Internal Server Error | Erro no servidor |
O que é Serialização/Desserialização?
- Serialização: Converter objeto Java/Kotlin → JSON
- Desserialização: Converter JSON → objeto Java/Kotlin
O Spring faz isso automaticamente usando a biblioteca Jackson!
// Objeto Kotlin
Invoice(id="123", customerId="CUST001", ...)
↓ Serialização (automática)
// JSON
{"id":"123","customerId":"CUST001",...}
O que é Validation?
Garantir que os dados recebidos estão corretos antes de processar.
Sem validação:
{
"customerId": "",
"amount": -100,
"status": "XPTO"
}
Isso causaria problemas! Com validação, rejeitamos automaticamente.
🎯 Próximos Passos
Depois de completar esta atividade, você deve:
- Commitar no Git - Salve seu progresso!
git add .
git commit -m "feat: adiciona endpoint POST /invoices e modelo Invoice"
- Testar mais cenários - Experimente diferentes dados
- Mostrar para o alguém - Demonstre funcionando!
💡 Dicas
Teste sempre que mudar algo!
Não acumule mudanças. Faça pequenas alterações e teste cada uma.Leia as mensagens de erro com atenção
Elas geralmente dizem exatamente qual é o problema. Procure a última linha da stack trace.Use logs para debug
Quando algo não funciona como esperado, adicionelogger.info()para ver o que está acontecendo.Não decore, entenda!
É melhor entender o conceito do que decorar código. Se entender, saberá adaptar para outros cenários.Postman é seu amigo
Salve suas requisições no Postman. Você vai reutilizá-las muito!
🎉 Parabéns
Se você chegou até aqui e seu endpoint está funcionando, você acabou de:
✅ Criar seu primeiro modelo de dados com validações
✅ Implementar seu primeiro endpoint REST POST
✅ Entender serialização JSON
✅ Testar APIs usando Postman/cURL
✅ Adicionar logs para debug
Você está oficialmente criando APIs profissionais! 🚀
Isso é incrível para alguém que há pouco tempo era estagiário! Você já está produzindo código que roda em produção em muitas empresas.
Continue assim! Cada linha de código é um passo a mais na sua jornada. 💪
Top comments (0)