Introduction: Why REST API on Swift is Fast and Modern
Developing mobile applications for iOS and macOS requires not only a beautiful interface but also a reliable server backend. REST API (Representational State Transfer) remains the de facto standard for data exchange between client and server. If you are already writing in Swift for the Apple ecosystem, creating an API in the same language is a logical step. This allows you to reuse data models, avoid code duplication, and implement changes faster.
In this article, we will show you how to write a full-fledged REST API in Swift using the Vapor framework — the most popular web framework for Swift. You will learn how to set up a project, create routes, handle JSON, and run the server. The entire process will take no more than 30 minutes. Ready? Let's go!
1. Environment Setup and Installing Vapor
Before writing code, make sure you have the necessary tools installed. Working with Vapor requires Swift 5.9 or newer (Swift 6 is recommended). You will also need a terminal and a code editor (Xcode or VS Code).
Installing Vapor Toolbox
Vapor Toolbox is a command-line utility that simplifies creating and managing projects. Install it via Homebrew:
brew install vaporIf Homebrew is not installed, download it from brew.sh. After installation, check the version:
vapor --versionCreating a New Project
Open the terminal and run the command:
vapor new MyAPI --template=apiThe --template=api flag creates a basic structure for a REST API without unnecessary files. Navigate to the project folder:
cd MyAPINow open the project in Xcode:
open Package.swiftOr use any other editor. Done! You have a clean Vapor template.
2. Basic Structure: Models, Controllers, and Routes
Vapor uses the MVC (Model-View-Controller) architecture adapted for server-side development. We will create a simple API for managing a To-Do list.
Creating the Model
A model is a structure that describes the data. In the Sources/App/Models/ folder, create a file Todo.swift:
import Fluentimport 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 }}We use Fluent — Vapor's ORM for working with databases. The model is automatically serialized to JSON thanks to the Content protocol.
Creating the Controller
The controller contains the request handling logic. Create a file Sources/App/Controllers/TodoController.swift:
import Vaporimport Fluent
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.delete(use: delete) } } // GET /todos — get all tasks func index(req: Request) async throws -> [Todo] { try await Todo.query(on: req.db).all() } // POST /todos — create a task func create(req: Request) async throws -> Todo { let todo = try req.content.decode(Todo.self) try await todo.save(on: req.db) return todo } // DELETE /todos/:id — delete a task func delete(req: R