Começando com Swift criando um CRUD com Vapor
Artigo
18 de outubro de 2024
Se você está acostumado a ver Swift apenas no contexto de desenvolvimento iOS, prepare-se para uma surpresa! Com o framework Vapor, podemos usar Swift para criar aplicações web robustas e eficientes. Neste guia amigável para iniciantes, vamos explorar como criar um CRUD (Create, Read, Update, Delete) para uma TODO List simples usando Vapor no backend e Neon como nosso serviço de banco de dados PostgreSQL.
O que é Swift e por que usá-lo para desenvolvimento web?
Swift é uma linguagem de programação moderna criada pela Apple. Embora seja mais conhecida pelo desenvolvimento iOS, seus recursos de segurança, velocidade e simplicidade também fazem dela uma excelente escolha para desenvolvimento web.
Introdução ao Vapor
Vapor é um framework web para Swift que nos permite criar APIs e aplicações web de forma rápida e simples. Ele oferece várias ferramentas que facilitam tarefas comuns do desenvolvimento web, como rotas, autenticação e interação com banco de dados.
O que vamos construir?
Vamos criar uma API para gerenciar uma lista de tarefas. Nossa API permitirá:
- Criar novas tarefas
- Listar todas as tarefas
- Ver detalhes de uma tarefa específica
- Atualizar uma tarefa existente
- Excluir uma tarefa
Pré-requisitos
Antes de começar, você precisará instalar:
- Swift: A linguagem de programação que vamos usar.
- Vapor Toolbox: Uma ferramenta de linha de comando que facilita a criação e o gerenciamento de projetos Vapor.
- Uma conta no Neon: Um serviço de banco de dados PostgreSQL na nuvem.
Não se preocupe se você nunca usou essas ferramentas antes. Vamos passar por cada etapa juntos!
Configuração do projeto
Vamos começar criando nosso projeto Vapor. Abra seu terminal e digite:
vapor new TodoListAPI
cd TodoListAPI
Isso cria um novo projeto Vapor chamado “TodoListAPI” e entra na pasta do projeto.
Agora, vamos configurar as dependências do projeto. Abra o arquivo Package.swift no seu editor de texto favorito e substitua o conteúdo por:
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "TodoListAPI",
platforms: [
.macOS(.v10_15)
],
dependencies: [
// 1. Main Vapor framework
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
// 2. Vapor's ORM for database interaction
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
// 3. Driver for PostgreSQL
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0")
],
targets: [
.target(
name: "App",
dependencies: [
.product(name: "Fluent", package: "fluent"),
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
.product(name: "Vapor", package: "vapor")
],
swiftSettings: [
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
]
),
.target(name: "Run", dependencies: [.target(name: "App")]),
.testTarget(name: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
])
]
)
Esse arquivo define as dependências do projeto. Estamos usando Vapor, Fluent (o ORM do Vapor) e o driver PostgreSQL.
Configuração do banco de dados
Agora, vamos configurar a conexão com o banco de dados. No arquivo configure.swift, dentro da pasta ./Sources/App/, adicione o seguinte código:
import Fluent
import FluentPostgresDriver
import Vapor
public func configure(_ app: Application) throws {
// Database configuration
app.databases.use(.postgres(
hostname: Environment.get("DATABASE_HOST") ?? "localhost",
port: Environment.get("DATABASE_PORT").flatMap(Int.init(_:)) ?? PostgresConfiguration.ianaPortNumber,
username: Environment.get("DATABASE_USERNAME") ?? "vapor_username",
password: Environment.get("DATABASE_PASSWORD") ?? "vapor_password",
database: Environment.get("DATABASE_NAME") ?? "vapor_database"
), as: .psql)
// Additional configurations will come here...
}
Essa configuração usa variáveis de ambiente para definir os detalhes da conexão com o banco. Para configurar essas variáveis, crie um arquivo chamado .env na raiz do projeto com o seguinte conteúdo:
DATABASE_HOST="your-neon-host.tech"
DATABASE_PORT=5432
DATABASE_USERNAME="your-username"
DATABASE_PASSWORD="your-password"
DATABASE_NAME="your-database"
Substitua os valores acima pelos valores fornecidos pelo Neon quando você criar seu banco de dados.
Criando o modelo de dados
Em Swift com Vapor, usamos “models” para representar nossas entidades de banco de dados. Vamos criar um modelo para nossas tarefas.
Crie um novo arquivo chamado Todo.swift na pasta Sources/App/Models e adicione o seguinte código:
import Fluent
import Vapor
final class Todo: Model, Content {
// 1. Name of the table in the database
static let schema = "todos"
// 2. Unique ID of the task
@ID(key: .id)
var id: UUID?
// 3. Title of the task
@Field(key: "title")
var title: String
// 4. Completion status of the task
@Field(key: "completed")
var completed: Bool
// 5. Empty initializer required by Fluent
init() { }
// 6. Custom initializer
init(id: UUID? = nil, title: String, completed: Bool = false) {
self.id = id
self.title = title
self.completed = completed
}
}
Esse modelo define a estrutura das nossas tarefas no banco de dados.
Criando a migration
Migrations são usadas para criar e atualizar a estrutura do banco de dados. Vamos criar uma migration para a tabela de tarefas.
Crie um novo arquivo chamado CreateTodo.swift na pasta Sources/App/Migrations:
import Fluent
struct CreateTodo: Migration {
// 1. Table creation
func prepare(on database: Database) -> EventLoopFuture<Void> {
return database.schema("todos")
.id()
.field("title", .string, .required)
.field("completed", .bool, .required, .custom("DEFAULT FALSE"))
.create()
}
// 2. Table removal (in case we need to revert the migration)
func revert(on database: Database) -> EventLoopFuture<Void> {
return database.schema("todos").delete()
}
}
Agora, adicione essa migration ao arquivo configure.swift:
app.migrations.add(CreateTodo())
Criando o controller
Controllers são responsáveis por gerenciar as operações de CRUD. Vamos criar um controller para nossas tarefas.
Crie um novo arquivo chamado TodoController.swift na pasta Sources/App/Controllers:
import Fluent
import Vapor
struct TodoController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let todos = routes.grouped("todos")
todos.get(use: index)
todos.post(use: create)
todos.group(":todoID") { todo in
todo.get(use: show)
todo.put(use: update)
todo.delete(use: delete)
}
}
// 1. List all tasks
func index(req: Request) throws -> EventLoopFuture<[Todo]> {
return Todo.query(on: req.db).all()
}
// 2. Create a new task
func create(req: Request) throws -> EventLoopFuture<Todo> {
let todo = try req.content.decode(Todo.self)
return todo.save(on: req.db).map { todo }
}
// 3. Show a specific task
func show(req: Request) throws -> EventLoopFuture<Todo> {
guard let todoID = req.parameters.get("todoID", as: UUID.self) else {
throw Abort(.badRequest)
}
return Todo.find(todoID, on: req.db)
.unwrap(or: Abort(.notFound))
}
// 4. Update an existing task
func update(req: Request) throws -> EventLoopFuture<Todo> {
guard let todoID = req.parameters.get("todoID", as: UUID.self) else {
throw Abort(.badRequest)
}
let updatedTodo = try req.content.decode(Todo.self)
return Todo.find(todoID, on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { todo in
todo.title = updatedTodo.title
todo.completed = updatedTodo.completed
return todo.save(on: req.db).map { todo }
}
}
// 5. Delete a task
func delete(req: Request) throws -> EventLoopFuture<HTTPStatus> {
guard let todoID = req.parameters.get("todoID", as: UUID.self) else {
throw Abort(.badRequest)
}
return Todo.find(todoID, on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { $0.delete(on: req.db) }
.transform(to: .noContent)
}
}
Configurando as rotas
Por fim, vamos configurar nossas rotas. No arquivo Sources/App/routes.swift, adicione:
import Fluent
import Vapor
func routes(_ app: Application) throws {
try app.register(collection: TodoController())
}
Executando e testando
Agora estamos prontos para executar nossa aplicação! No terminal, rode:
vapor run migrate
vapor run
O primeiro comando executa nossas migrations, criando a tabela no banco de dados. O segundo inicia o servidor.
Você pode testar a API usando um cliente HTTP como Postman ou cURL:
- Criar uma tarefa (POST):
http://localhost:8080/todos
Corpo da requisição:{"title": "Learn Swift", "completed": false} - Listar todas as tarefas (GET):
http://localhost:8080/todos - Buscar uma tarefa específica (GET):
http://localhost:8080/todos/{id} - Atualizar uma tarefa (PUT):
http://localhost:8080/todos/{id}
Corpo da requisição:{"title": "Learn Swift and Vapor", "completed": true} - Excluir uma tarefa (DELETE):
http://localhost:8080/todos/{id}
Conclusão
Parabéns! Você acabou de criar sua primeira API RESTful usando Swift e Vapor. Isso é apenas o começo do que você pode fazer com essas ferramentas poderosas.
Algumas vantagens de usar Swift para desenvolvimento web incluem:
- Segurança de tipos: Swift é uma linguagem fortemente tipada, o que ajuda a evitar muitos erros comuns.
- Performance: Swift é conhecida por sua alta performance.
- Sintaxe moderna e clara: A sintaxe do Swift foi projetada para ser fácil de ler e escrever.
- Ecossistema em crescimento: Com frameworks como Vapor, o ecossistema de desenvolvimento web em Swift está em constante expansão.
Continue explorando e construindo com Swift e Vapor. Existe um mundo de possibilidades esperando por você!