Введение: Swift — не только для мобильных приложений
Многие разработчики привыкли ассоциировать Swift исключительно с iOS и macOS. Однако этот язык давно вышел за пределы экосистемы Apple. С появлением серверных фреймворков, таких как Vapor и Kitura, Swift стал полноценным инструментом для создания бэкенда. В этой статье мы за 30 минут напишем полноценный REST API на Swift с использованием фреймворка Vapor. Вы узнаете, как настроить проект, создать эндпоинты, подключить базу данных и обработать ошибки. Готовы? Поехали!
Шаг 1: Установка и настройка проекта
Для начала убедитесь, что у вас установлен Swift 5.5 или новее. Проверить версию можно командой:
swift --version
Далее установите Vapor Toolbox — утилиту для управления проектами:
brew install vapor
Теперь создадим новый проект:
vapor new MyAPI --template=api
Эта команда создаст структуру папок с готовым шаблоном API. Перейдите в папку проекта и запустите сборку:
cd MyAPI
vapor build
Если сборка прошла успешно, запустите сервер:
vapor run
По умолчанию сервер будет доступен по адресу http://127.0.0.1:8080. Откройте браузер и перейдите по этому адресу — вы должны увидеть приветственное сообщение.
Структура проекта Vapor
- Sources/App — основная директория с кодом приложения
- Sources/App/Controllers — контроллеры для обработки запросов
- Sources/App/Models — модели данных
- Sources/App/routes.swift — файл для регистрации маршрутов
- configure.swift — настройка приложения и базы данных
Шаг 2: Создаем модель данных и базу данных
Для примера создадим простое API для управления списком задач (Todo). Начнем с модели данных. Создайте файл Todo.swift в папке Sources/App/Models:
import Fluent
import Vapor
final class Todo: Model, Content {
static let schema = "todos"
@ID(key: .id)
var id: UUID?
@Field(key: "title")
var title: String
@Field(key: "is_completed")
var isCompleted: Bool
init() {}
init(id: UUID? = nil, title: String, isCompleted: Bool = false) {
self.id = id
self.title = title
self.isCompleted = isCompleted
}
}
Теперь создадим миграцию для таблицы. В файле Sources/App/Migrations/CreateTodo.swift:
import Fluent
struct CreateTodo: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema("todos")
.id()
.field("title", .string, .required)
.field("is_completed", .bool, .required)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema("todos").delete()
}
}
Не забудьте зарегистрировать миграцию в configure.swift:
app.migrations.add(CreateTodo())
Для простоты будем использовать SQLite (встроенная база данных). Добавьте в configure.swift:
app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
Шаг 3: Создаем эндпоинты REST API
Теперь самое интересное — реализация CRUD-операций. Создайте контроллер TodoController.swift в папке Sources/App/Controllers:
import Vapor
struct TodoController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let todos = routes.grouped("api", "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)
}
}
// GET /api/todos — список всех задач
func index(req: Request) async throws -> [Todo] {
try await Todo.query(on: req.db).all()
}
// POST /api/todos — создание новой задачи
func create(req: Request) async throws -> Todo {
let todo = try req.content.decode(Todo.self)
try await todo.save(on: req.db)
return todo
}
// GET /api/todos/:id — получение одной задачи
func show(req: Request) async throws -> Todo {
guard let todo = try await Todo.find(req.parameters.get("todoID"), on: req.db) else {
throw Abort(.notFound)
}
return todo
}
// PUT /api/todos/:id — обновление задачи
func update(req: Request) async throws -> Todo {
guard let todo = try await Todo.find(req.parameters.get("todoID"), on: req.db) else {
throw Abort(.notFound)
}
let updatedTodo = try req.content.decode(Todo.self)
todo.title = updatedTodo.title
todo.isCompleted = updatedTodo.isCompleted
try await todo.save(on: req.db)
return todo
}
// DELETE /api/todos/:id — удаление задачи
func delete(req: Request) async throws -> HTTPStatus {
guard let todo = try await Todo.find(req.parameters.get("todoID"), on: req.db) else {
throw Abort(.notFound)
}
try await todo.delete(on: req.db)
return .ok
}
}
Зарегистрируйте контроллер в routes.swift:
func routes(_ app: Application) throws {
try app.register(collection: TodoController())
}
Шаг 4: Тестирование API и обработка ошибок
Запустите сервер и протестируйте эндпоинты. Вот пример запроса через curl:
# Создать задачу
curl -X POST http://127.0.0.1:8080/api/todos \
-H "Content-Type: application/json" \
-d '{"title":"Изучить Vapor","isCompleted":false}'
# Получить все задачи
curl http://127.0.0.1:8080/api/todos
# Обновить задачу
curl -X PUT http://127.0.0.1:8080/api/todos/{ID} \
-H "Content-Type: application/json" \
-d '{"title":"Изучить Vapor","isCompleted":true}'
# Удалить задачу
curl -X DELETE http://127.0.0.1:8080/api/todos/{ID