Como criar uma API com Swift e Vapor

Silvia endoscopia.
O ecossistema Swift não se limita apenas ao desenvolvimento de aplicativos para iPhone, iPad e macOS.

Nos últimos anos, o desenvolvimento backend com Swift evoluiu bastante, principalmente graças ao Vapor.

O Vapor é atualmente o framework web mais popular do ecossistema Swift e permite criar:

Tudo isso utilizando Swift puro.

Neste guia, você vai aprender como criar uma API REST completa utilizando Swift e Vapor, incluindo:

O objetivo aqui é servir como uma introdução moderna para quem está começando com backend em Swift.

O que é o Vapor?

O Vapor é um framework server-side escrito em Swift e baseado no SwiftNIO, a infraestrutura assíncrona criada pela Apple para aplicações de rede de alta performance.

Na prática, ele oferece:

Hoje o Vapor é amplamente utilizado para:

Por que usar Swift no backend?

Se você já desenvolve para Apple Platforms, usar Swift no backend traz várias vantagens:

Além disso, o Swift moderno possui desempenho extremamente competitivo no lado servidor.

Instalando o Vapor Toolbox

O Vapor possui uma CLI oficial chamada Vapor Toolbox.

Ela facilita:

macOS

A forma mais simples é via Homebrew:

brew install vapor

Linux

Também é possível instalar utilizando Homebrew:

brew install vapor

Ou manualmente:

git clone https://github.com/vapor/toolbox.git
cd toolbox
swift build -c release
sudo cp .build/release/vapor /usr/local/bin/

Verificando a instalação

Depois execute:

vapor --help

Se o comando funcionar, a CLI está pronta.

Criando o primeiro projeto

Agora vamos criar uma API simples chamada:

tasks-api

Execute:

vapor new tasks-api

Durante a criação escolha:

O que o Vapor criou?

A estrutura inicial já vem organizada:

tasks-api/
├── Package.swift
├── Sources/
├── Tests/
├── Public/
└── Resources/

Os arquivos mais importantes inicialmente são:

Arquivo Função
configure.swift Configuração da aplicação
routes.swift Rotas HTTP
Package.swift Dependências do projeto

Entrando no projeto

cd tasks-api

Abrindo no editor

VS Code

code .

Xcode

open Package.swift

Executando a aplicação

Para iniciar o servidor:

swift run

O Vapor iniciará algo parecido com:

Server starting on http://127.0.0.1:8080

Acesse:

http://localhost:8080

Se tudo estiver correto, o servidor já estará funcionando.

Criando a model Task

Agora vamos criar nossa primeira entidade.

Arquivo:

Sources/App/Models/Task.swift

Model completa

import Fluent
import Vapor

final class Task: Model, Content {
    static let schema = "tasks"

    @ID(key: .id)
    var id: UUID?

    @Field(key: "title")
    var title: String

    @Field(key: "done")
    var done: Bool

    @Timestamp(key: "created_at", on: .create)
    var createdAt: Date?

    init() {}

    init(
        id: UUID? = nil,
        title: String,
        done: Bool = false
    ) {
        self.id = id
        self.title = title
        self.done = done
    }
}

O que esse model faz?

Essa estrutura:

Criando a migration

Agora precisamos criar a estrutura do banco.

Gerando migration

vapor generate migration CreateTasks

Arquivo:

Sources/App/Migrations/CreateTasks.swift

Migration completa

import Fluent

struct CreateTasks: Migration {
    func prepare(
        on database: Database
    ) -> EventLoopFuture<Void> {

        database.schema("tasks")
            .id()
            .field("title", .string, .required)
            .field("done", .bool, .required)
            .field("created_at", .datetime)
            .create()
    }

    func revert(
        on database: Database
    ) -> EventLoopFuture<Void> {

        database.schema("tasks").delete()
    }
}

Registrando a migration

No arquivo:

Sources/App/configure.swift

Adicione:

app.migrations.add(CreateTasks())

Executando migration

vapor run migrate

O SQLite será criado automaticamente.

Criando o controller

Agora vamos criar o CRUD da API.

Gerando controller

vapor generate controller TaskController

Arquivo:

Sources/App/Controllers/TaskController.swift

Controller completo

import Vapor
import Fluent

struct TaskController: RouteCollection {

    func boot(
        routes: RoutesBuilder
    ) throws {

        let tasks = routes.grouped("tasks")

        tasks.post(use: create)
        tasks.get(use: index)
        tasks.get(":id", use: show)
        tasks.put(":id", use: update)
        tasks.delete(":id", use: delete)
    }

    func create(
        req: Request
    ) async throws -> Task {

        let task = try req.content.decode(Task.self)

        try await task.save(on: req.db)

        return task
    }

    func index(
        req: Request
    ) async throws -> [Task] {

        try await Task.query(on: req.db).all()
    }

    func show(
        req: Request
    ) async throws -> Task {

        guard let task = try await Task.find(
            req.parameters.get("id"),
            on: req.db
        ) else {
            throw Abort(.notFound)
        }

        return task
    }

    func update(
        req: Request
    ) async throws -> Task {

        let updated = try req.content.decode(Task.self)

        guard let task = try await Task.find(
            req.parameters.get("id"),
            on: req.db
        ) else {
            throw Abort(.notFound)
        }

        task.title = updated.title
        task.done = updated.done

        try await task.save(on: req.db)

        return task
    }

    func delete(
        req: Request
    ) async throws -> HTTPStatus {

        guard let task = try await Task.find(
            req.parameters.get("id"),
            on: req.db
        ) else {
            throw Abort(.notFound)
        }

        try await task.delete(on: req.db)

        return .noContent
    }
}

Registrando as rotas

Arquivo:

Sources/App/routes.swift

Adicione:

func routes(_ app: Application) throws {
    try app.register(
        collection: TaskController()
    )
}

Testando a API

Criando uma task

curl -X POST http://localhost:8080/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Estudar Vapor","done":false}'

Listando tasks

curl http://localhost:8080/tasks

Atualizando task

curl -X PUT http://localhost:8080/tasks/{id} \
-H "Content-Type: application/json" \
-d '{"title":"Atualizado","done":true}'

Removendo task

curl -X DELETE http://localhost:8080/tasks/{id}

Vapor precisa de NGINX?

Tecnicamente, não.

O próprio Vapor já possui servidor HTTP embutido baseado em SwiftNIO.

Você pode simplesmente executar:

swift run

E a aplicação já estará servindo requisições HTTP.

Mas em ambientes profissionais normalmente utilizamos um proxy reverso na frente da aplicação.

Por que usar NGINX?

O NGINX normalmente fica responsável por:

Na prática:

Internet → NGINX → Vapor

Esse é o modelo mais comum em produção.

Dockerizando a aplicação

Agora vamos criar um ambiente moderno utilizando Docker.

Dockerfile

Crie:

Dockerfile

Conteúdo

FROM swift:6.0-jammy

WORKDIR /app

COPY . .

RUN swift build -c release

EXPOSE 8080

CMD [".build/release/Run"]

Build da imagem

docker build -t tasks-api .

Executando container

docker run -p 8080:8080 tasks-api

A aplicação já estará acessível em:

http://localhost:8080

Usando Docker Compose + NGINX

Agora vamos criar uma estrutura mais profissional.

Estrutura

project/
├── docker-compose.yml
├── nginx/
│   └── default.conf
└── tasks-api/

docker-compose.yml

services:
  vapor:
    build: .
    container_name: vapor-api
    restart: always
    expose:
      - "8080"

  nginx:
    image: nginx:latest
    container_name: nginx
    restart: always
    ports:
      - "80:80"

    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf

    depends_on:
      - vapor

Configuração do NGINX

Arquivo:

nginx/default.conf

Conteúdo

server {
    listen 80;

    location / {
        proxy_pass http://vapor:8080;

        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Iniciando tudo

docker compose up --build

Agora:

Internet → NGINX → Vapor Container

Por que Docker faz sentido com Vapor?

O Docker ajuda bastante em:

Hoje praticamente todo ambiente backend moderno utiliza containers.

Vapor é rápido?

Sim.

O Vapor possui excelente performance graças ao SwiftNIO.

Na prática, ele compete muito bem com:

Além disso, o consumo de memória costuma ser bastante eficiente.

Vale a pena aprender Vapor em 2026?

Sim, principalmente se você já trabalha com Swift.

O framework evoluiu bastante e hoje oferece:

Conclusão

O Vapor transformou o Swift em uma linguagem extremamente interessante também para backend.

Com poucas linhas já é possível criar:

E combinado com Docker e NGINX, você já possui uma arquitetura bastante profissional para iniciar projetos reais.

Para quem já vive no ecossistema Apple, aprender Vapor é provavelmente o caminho mais natural para entrar no desenvolvimento backend utilizando Swift.