Building REST API with Swift: A Comprehensive Guide

Building REST API with Swift: A Comprehensive Guide

Swift is a powerful and intuitive programming language for macOS, iOS, watchOS, tvOS and beyond. With its modern syntax and compiled nature, it’s a great language to write server-side applications. In this guide, we’ll walk through the basics of building a RESTful API with Swift using Vapor 3.

We’ll start by creating a new project and setting up the basic structure. Then, we’ll dive into how to define models and routes for our API. Finally, we’ll look at how to include authentication and authorization in our application.

Creating a New Project

Before we can start building our API, we need to create a new project. To do this, we’ll use the Vapor Toolbox. The Vapor Toolbox provides a number of helpful commands for working with Vapor projects. First, make sure you have the toolbox installed. You can find installation instructions here.

Once you’ve installed the Toolbox, open up a terminal window and navigate to the directory where you want to create the project. Then, run the following command:

vapor new MyAPI

This will create a new Vapor project with the name “MyAPI”. Now that we have our project set up, let’s take a look at the structure.

Project Structure

Vapor projects are structured in a familiar Model-View-Controller (MVC) pattern. The model layer is responsible for defining your data and relationships. The controller layer is responsible for handling requests and returning responses. And the view layer is responsible for rendering templates.

The project structure looks like this:

MyAPI/
├── App/
│   ├── Controllers/
│   ├── Models/
│   ├── Resources/
│   └── Views/
├── Public/
├── Resources/
└── Tests/

The App directory contains the model, controller, and view layers of the application. The Public directory contains any static assets such as images, stylesheets, or JavaScript files. The Resources directory contains configuration and localization files. And the Tests directory contains unit and integration tests.

Defining Models

Now that we have our project set up, let’s start building our API. The first step is to define our models. Models are classes that represent the data in our application. They define the structure of our data and the relationships between different pieces of data.

In Vapor, models are defined using the Fluent ORM. Fluent is an object-relational mapper (ORM) that makes it easy to work with databases in Swift. It provides a simple, expressive syntax for defining models and their relationships.

Let’s define a simple User model:

// 1
final class User: Codable {
    // 2
    var id: Int?
    var name: String
    var email: String
    
    // 3
    init(name: String, email: String) {
        self.name = name
        self.email = email
    }
}

// 4
extension User: Migration { }

// 5
extension User: Content { }

// 6
extension User: Parameter { }

Here’s what’s happening in the code above:

1. We define a new class called User that conforms to the Codable protocol.
2. We define the properties of our User model.
3. We provide an initializer for our User model.
4. We extend our User model to conform to the Migration protocol. This allows us to create the necessary database tables when our application starts up.
5. We extend our User model to conform to the Content protocol. This allows us to encode and decode our model to and from various formats, such as JSON.
6. We extend our User model to conform to the Parameter protocol. This allows us to use our model as a route parameter.

Defining Routes

Now that we’ve defined our models, let’s define the routes for our API. Routes define the URLs that our application responds to, as well as the logic that handles those requests.

Routes are defined using a fluent API. The API provides a simple, expressive syntax for defining routes and their associated handlers. Let’s define a route for our User model:

// 1
router.get("users") { req -> Future<[User]> in
    // 2
    return User.query(on: req).all()
}

// 3
router.post("users") { req -> Future<User> in
    // 4
    return try req.content.decode(User.self).flatMap { user in
        // 5
        return user.save(on: req)
    }
}

// 6
router.get("users", User.parameter) { req -> Future<User> in
    // 7
    return try req.parameters.next(User.self)
}

// 8
router.put("users", User.parameter) { req -> Future<User> in
    // 9
    return try flatMap(to: User.self, 
                       req.parameters.next(User.self), 
                       req.content.decode(User.self)) { user, updatedUser in
        
        // 10
        user.name = updatedUser.name
        user.email = updatedUser.email
        
        // 11
        return user.save(on: req)
    }
}

// 12
router.delete("users", User.parameter) { req -> Future<HTTPStatus> in
    // 13
    return try req.parameters.next(User.self)
        .delete(on: req)
        .transform(to: HTTPStatus.noContent)
}

Here’s what’s happening in the code above:

1. We define a route that handles GET requests to the “/users” path. The route handler returns a list of all users.
2. We use Fluent to query the database for all users.
3. We define a route that handles POST requests to the “/users” path. The route handler creates a new user.
4. We decode the request body into a User object.
5. We save the user to the database.
6. We define a route that handles GET requests to the “/users/:id” path. The route handler returns a single user.
7. We use Fluent to query the database for the user with the specified ID.
8. We define a route that handles PUT requests to the “/users/:id” path. The route handler updates an existing user.
9. We decode the request body into an updated User object.
10. We update the user’s properties with the values from the updated User object.
11. We save the updated user to the database.
12. We define a route that handles DELETE requests to the “/users/:id” path. The route handler deletes an existing user.
13. We use Fluent to delete the user with the specified ID.

Adding Authentication & Authorization

Finally, let’s look at how to add authentication and authorization to our API. Authentication is the process of verifying a user’s identity. Authorization is the process of determining whether a user has permission to access a particular resource.

In Vapor, authentication and authorization are handled using the Authentication package. The package provides an easy way to add authentication and authorization to your Vapor applications.

Let’s add authentication to our API:

// 1
let authService = try AuthenticationService(config: authConfig)

// 2
authService.register(strategy: BasicAuthentication.self)

// 3
let basicAuthMiddleware = User.basicAuthMiddleware(using: authService)

// 4
let protectedRoutes = router.grouped([basicAuthMiddleware])

// 5
protectedRoutes.get("users") { req -> Future<[User]> in
    // 6
    let user = try req.requireAuthenticated(User.self)
    guard user.isAdmin else {
        throw Abort(.unauthorized)
    }
    
    return User.query(on: req).all()
}

// 7
protectedRoutes.post("users") { req -> Future<User> in
    // 8
    let user = try req.requireAuthenticated(User.self)
    guard user.isAdmin else {
        throw Abort(.unauthorized)
    }
    
    return try req.content.decode(User.self).flatMap { user in
        return user.save(on: req)
    }
}

Here’s what’s happening in the code above:

1. We create an AuthenticationService with the specified configuration.
2. We register the BasicAuthentication strategy with the authentication service.
3. We create a middleware that requires authentication using the BasicAuthentication strategy.
4. We wrap our routes in the authentication middleware.
5. We update our GET “/users” route to require authentication and authorization.
6. We require the user to be authenticated and check if they are an admin.
7. We update our POST “/users” route to require authentication and authorization.
8. We require the user to be authenticated and check if they are an admin.

Conclusion

In this guide, we’ve seen how to build a RESTful API with Swift using Vapor 3. We started by creating a new project and setting up the basic structure. Then, we looked at how to define models and routes for our API. Finally, we looked at how to add authentication and authorization to our application.

Building APIs with Swift and Vapor is a powerful and enjoyable experience. With its modern syntax and compiled nature, it’s a great language to write server-side applications. I hope this guide has given you a good introduction to building APIs with Swift and Vapor.

Scroll to Top