Integration Architecture

Articles by Mike Gough.

Published on 02 Feb 2020.🕑 5 minutes read.

Creating Swift microservices using Vapor and MongoDB

Learn how to create a server-side RESTful API using Swift and Vapor backed with a NoSQL database for storage and run it all using Docker.

Prerequisites

To keep things simple we will assume you have access to a machine running macOS or Linux and have already installed Curl, Swift, Vapor and Docker.

Creating a MongoDB instance

For this tutorial we will need to setup a local MongoDB instance that we can use for our Todo API. The following command starts a MongoDB container instance, allowing us to execute MongoDB statements against a database instance:

docker run -d \
  --name mongodb \
  -p 27017-27019:27017-27019 \
  -v ~/data:/data/db \
  mongo:latest

Creating a Vapor project

For this post we will make use of the built in Vapor code generator to create a simple RESTful API for creating, reading, updating and deleting todos. Use vapor to create a new project by running the following command:

vapor new vapor-todo-api

Once the project files have been generated, navigate into the directory by running:

cd vapor-todo-api

In order to connect our Vapor application to a MongoDB database we have to modify some of the generated code to remove the use of SQLite. Let's replace the SQLite dependency and target with MeowVapor inside the Package.swift file:

// swift-tools-version:4.0
import PackageDescription

let package = Package(
    name: "vapor-todo-api",
    products: [
        .library(
            name: "vapor-todo-api", 
            targets: ["App"]
        ),
    ],
    dependencies: [
        // 💧 A server-side Swift web framework.
        .package(
            url: "https://github.com/vapor/vapor.git", 
            from: "3.0.0"
        ),

        // 🔵 Swift ORM (queries, models, relations, etc) built on Meow, MongoKitten.
        .package(
            url: "https://github.com/OpenKitten/MeowVapor.git", 
            from: "2.1.2"
        )
    ],
    targets: [
        .target(
            name: "App", 
            dependencies: [
                "MeowVapor", 
                "Vapor"
            ]
        ),
        .target(
            name: "Run", 
            dependencies: [
                "App"
            ]
        ),
        .testTarget(
            name: "AppTests", 
            dependencies: [
                "App"
            ]
        )
    ]
)

MeowVapor is a wrapper for Meow and MongoKitten and provides us with a boiletplate-free object persitance framework for MongoDB and Swift, freeing us from the need to manage our database. After adding this dependancy we need to modify the generated Sources/App/configure.swift file in order to set up the MongoDB driver instead of the default SQLite one. We are going to start by setting the database connection details based on an environment variable MONGODB_URI:

import MeowVapor
import Vapor

// Called before your application initializes.
public func configure(
    _ config: inout Config, 
    _ env: inout Environment, 
    _ services: inout Services) throws {
    
    let uri = Environment.get("MONGODB_URI")
        ?? "mongodb://localhost/tododb"

    // Configure a MongoDB database
    let meow = try MeowProvider(uri: uri)
    
    // Register providers first
    try services.register(meow)

    // Register routes to the router
    let router = EngineRouter.default()
    try routes(router)
    services.register(router, as: Router.self)

    // Register middleware
    var middlewares = MiddlewareConfig()
    middlewares.use(ErrorMiddleware.self)
    services.register(middlewares)
}

Next, we need to change to the Sources/Models/Todo.swift file so that our Todo model includes an attributed called _id which will be used to store a unique identifier of the type ObjectId for our MongoDB documents:

import MeowVapor
import Vapor

// A single entry of a Todo list.
final class Todo: Model {
    
    // A unique identifier for the `Todo`
    var _id: ObjectId

    // A title describing what this `Todo` entails.
    var title: String

    // Creates a new `Todo`.
    init(_id: ObjectId, title: String) {
        self._id = _id
        self.title = title
    }
    
    // Creates a new `Todo`.
    init(title: String) {
        self._id = ObjectId()
        self.title = title
    }
}

// Allows `Todo` to be encoded to and decoded from HTTP messages.
extension Todo: Content { }

// Allows `Todo` to be used as a dynamic parameter in route definitions.
extension Todo: Parameter {}

Then we need to update our routes inside the Sources/App/routes.swift file to support the GET, POST, PUT end DELETE methods for the Todo's endpoint:

import Vapor
import MeowVapor

// Register your application's routes here.
public func routes(_ router: Router) throws {
    let todoController = TodoController()
    
    router.get("todos", use: todoController.index)
    router.post("todos", use: todoController.create)
    router.put("todos", use: todoController.upsert)
    router.delete("todos", Todo.parameter, use: todoController.delete)
}

Lastly we will need to build and run our application. To create a Docker image containing our Vapor project, we can use the web.Dockerfile file that was automatically generated for us by running the following command:

docker build --build-arg env=docker -t vapor-todo-api-image -f web.Dockerfile

This command may take some time to finish, but when complete we will have made a Docker image containing our compiled Vapor project. The container can be run using the command:

docker run --name vapor-todo-api -p 8080:80 vapor-todo-api-image

Testing the Todo API

Now that we have a running instance of a MongoDB database and our API, let's test it to ensure it works as expected. To insert a new Todo we can call the POST endpoint that we created using the Curl command:

curl -X POST \
  -H "Content-Type: application/json" -d '{"_id":"5e36366465da966614a18f46", "title":"My todo!"}' \
   localhost/todos

After running this command you should recieve a copy of the created Todo in the API response:

{"_id":"5e36366465da966614a18f46", "title":"My todo!"}

To verify that our Todo was indeed created, we can use our GET endpoint:

curl -X GET localhost/todos

This should print the following to the screen:

[{"_id":"5e36366465da966614a18f46","title":"My Todo!"}

Now that we have verified our Todo was indded created, lets replace it using our PUT endpoint by running:

curl -X PUT \
  -H "Content-Type: application/json" -d '{"_id":"5e36366465da966614a18f46", "title":"My updated todo!"}' \
  localhost/todos

You should recieve a copy of the updated Todo in the API response:

{"_id":"5e36366465da966614a18f46", "title":"My updated todo!"}

Finally we can delete our Todo using our DELETE endpoint by running:

curl -X DELETE localhost/todos/5e36366465da966614a18f48

This should return a HTTP 200 response.

Summary

That's it! In this post we have demonstrated how to build an RESTful API in Swift that allows you to create, read, update and delete Todo's inside of a MongoDB database. Future posts will look at how we can deploy microservices using kubernetes.

References