Как написать REST API на Swift за 30 минут: практическое руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: 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                

Блоги

Рекомендаций по книгам