Introduction: Swift — Not Just for Mobile Apps
Many developers are accustomed to associating Swift exclusively with iOS and macOS. However, this language has long moved beyond the Apple ecosystem. With the emergence of server-side frameworks such as Vapor and Kitura, Swift has become a full-fledged tool for backend development. In this article, we will write a complete REST API in Swift using the Vapor framework in 30 minutes. You will learn how to set up a project, create endpoints, connect a database, and handle errors. Ready? Let's go!
Step 1: Installing and Setting Up the Project
First, make sure you have Swift 5.5 or newer installed. You can check the version with the command:
swift --version
Next, install Vapor Toolbox — a utility for managing projects:
brew install vapor
Now let's create a new project:
vapor new MyAPI --template=api
This command will create a folder structure with a ready-made API template. Navigate to the project folder and run the build:
cd MyAPI
vapor build
If the build is successful, start the server:
vapor run
By default, the server will be available at http://127.0.0.1:8080. Open your browser and go to this address — you should see a welcome message.
Vapor Project Structure
- Sources/App — the main directory with application code
- Sources/App/Controllers — controllers for handling requests
- Sources/App/Models — data models
- Sources/App/routes.swift — file for registering routes
- configure.swift — application and database configuration
Step 2: Creating the Data Model and Database
For this example, we'll create a simple API for managing a to-do list. Let's start with the data model. Create a file Todo.swift in the Sources/App/Models folder:
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
}
}
Now let's create a migration for the table. In the file 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()
}
}
Don't forget to register the migration in configure.swift:
app.migrations.add(CreateTodo())
For simplicity, we'll use SQLite (a built-in database). Add this to configure.swift:
app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
Step 3: Creating REST API Endpoints
Now for the most interesting part — implementing CRUD operations. Create a controller TodoController.swift in the Sources/App/Controllers folder:
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)