We're hiring
Products

Channels

Beams

Chatkit

DocsTutorialsSupportCareersPusher Blog
Sign InSign Up
Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Sign InSign Up

Create an iOS chat app with Vapor - Part 1: Setting up the backend

  • Christopher Batin
January 21st, 2019
You will need Xcode 10 and Vapor 3.0. Some understanding of Vapor will be helpful.

Introduction

In this tutorial we will be creating a new iOS chat app using Chatkit with a Vapor backend that handles creation of our users. At the time of writing there is no official Chatkit server SDK so we will be interacting directly with the Pusher Chatkit API.

Prerequisites

  • Understanding of Vapor - Please complete:
  • Xcode 10 and the latest Xcode command line tools.
  • MacOS
  • Vapor 3.0 - Install instructions here.
  • An understanding of iOS development and Xcode environment.

Setup

Open terminal and move to your working directory. Create a new Vapor project by entering the following command:

    $ vapor new SuperChatServer
    $ cd SuperChatServer
    $ vapor build
    $ vapor xcode -y

This will create a new Vapor project, build the project and open it within Xcode. Once Xcode opens remember to set the run scheme correctly.

Add dependencies

Open your Package.swift file and replace the contents with the following.

Note: Leave the line swift-tools-version:X.X as it was when you opened the file.

    // ./Package.swift
    // swift-tools-version:4.2
    import PackageDescription

    let package = Package(
        name: "SuperChatServer",
        dependencies: [
            // A server-side Swift web framework.
            .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
            .package(url: "https://github.com/PerfectlySoft/Perfect-Crypto.git", from: "3.0.0"),
            // Swift ORM (queries, models, relations, etc) built on SQLite 3.
            .package(url: "https://github.com/vapor/fluent-sqlite.git", from: "3.0.0")
        ],
        targets: [
            .target(name: "App", dependencies: ["FluentSQLite", "Vapor", "PerfectCrypto"]),
            .target(name: "Run", dependencies: ["App"]),
            .testTarget(name: "AppTests", dependencies: ["App"])
        ]
    )

Now close Xcode and run the following commands to install the package and reopen Xcode:

    $ swift package update
    $ vapor xcode -y

Set up the SQLite database

We already have our SQLite dependency installed as this comes as default. To configure it we need to make some changes to our configure.swift.

    // ../Sources/App/configure.swift
    import FluentSQLite
    import Vapor
    /// Called before your application initializes.
    public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
        /// Register providers first
        try services.register(FluentSQLiteProvider())

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

        /// Register middleware
        var middlewares = MiddlewareConfig() // Create _empty_ middleware config
        /// middlewares.use(FileMiddleware.self) // Serves files from `Public/` directory
        middlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response
        services.register(middlewares)

        // Configure a SQLite database
        /// Register the configured SQLite database to the database config.
        var databases = DatabasesConfig()
        try databases.add(database: SQLiteDatabase(storage: .memory), as: .sqlite)
    //    try databases.add(database: SQLiteDatabase(storage: .file(path: "db.sqlite")),
    //                      as: .sqlite)
        services.register(databases)

    }

The important section here is line:

    try databases.add(database: SQLiteDatabase(storage: .memory), as: .sqlite)

We’ve set the database to use the memory storage. This means that every time we restart our server the database will be reset. This is perfect for debugging if we want persistent storage we can change this line to be:

     try databases.add(database: SQLiteDatabase(storage: .file(path: "db.sqlite")), 
                       as: .sqlite)

You will also notice that we have added our user migration in here as well ready for creating our model.

Create the model

We need to create a new model for our user. Close Xcode and open terminal at your working directly and enter the following commands to create a new file and reopen Xcode.

    $ touch Sources/App/Models/User.swift
    $ vapor xcode -y

Open your User.swift file and add the following:

     // ../Sources/App/Models/User.swift
     import Vapor
     import FluentSQLite

     final class User: Codable {
        var id: UUID?
        var name: String

        init(name: String) {
            self.name = name
        }

        init(id: UUID?, name: String) {
            self.id = id
            self.name = name
        }
     }

    extension User: Content {}
    extension User: SQLiteUUIDModel {
        static func prepare(on connection: SQLiteConnection)
            -> Future<Void> {
                return Database.create(self, on: connection) { builder in
                    try addProperties(to: builder)
                    builder.unique(on: \.name)
                }
        }
     }
    extension User: Migration {}
    extension User: Parameter {}

Here we are creating our user type. We are saying that the ID field for the database is a UUID that we will provide. We will be able to use this UUID to create our user on the Pusher platform as well. We have also provided a constraint on the name field and said that this must be unique. If we try to create a new row in the database with a name that already exists it will fail.

Now reopen your configure.swift file and below the line services.register(databases) add the following to configure our migrations.

        /// Configure migrations
        var migrations = MigrationConfig()
        migrations.add(model: User.self, database: .sqlite)
        services.register(migrations)

Set up Chatkit

Open your Pusher dashboard or create a free account by following this link, create a new Chatkit instance and name it SuperChat.

Once your instance is created go to your credentials tab. Make a note of your instance locator and your secret key you will need them later. You will also need to turn on your test token provider, we will need this for the second part of this tutorial.

Your instance locator is in the form:

    v1:us1:YOUR_INSTANCE_ID

Your secret key is in the form:

    API_KEY_ID:SECRET_KEY

You will need to make note of your all of these formats. You will need them for interacting with the API endpoints directly. Your test token endpoint has an example of its use.

Create the user controller

We need to create a new controller for handling of user creation. Close Xcode and open terminal at your working directly and enter the following commands to create a new file and reopen Xcode.

    $ touch Sources/App/Controllers/UserController.swift
    $ vapor xcode -y

Open your UserController.swift file and add the following:

    // ../Sources/App/Controllers/UserController.swift
    import Vapor
    import Foundation
    import PerfectCrypto
    import FluentSQLite

    /// Controls basic CRUD operations on `Todo`s.
    final class UserController {
        func find(_ req: Request) throws -> Future<User> {
            return try req.content.decode(User.self).flatMap({ user in
                return User.query(on: req).filter(\.name == user.name).first().map(to: User.self, { user in
                    guard let user = user else {
                        throw Abort(.notFound)
                    }
                    return user
                })
            })
        }

        func create(_ req: Request) throws -> Future<User> {
            return try req.content.decode(User.self).flatMap { user in
                let chatkitEndPoint = "https://us1.pusherplatform.io/services/chatkit/v2/YOUR_INSTANCE_ID/users"
                guard let url = URL(string: chatkitEndPoint) else {
                    throw Abort.init(HTTPResponseStatus.internalServerError)
                }
                user.id = UUID.init()
                let newUser = user.create(on: req)
                newUser.save(on: req).whenSuccess({ _ in
                    let bearer = BearerAuthorization.init(token: AuthController.createJWToken())
                    _ = try! req.client().post(url) { post in
                        post.http.headers.bearerAuthorization = bearer
                        post.http.headers.add(name: HTTPHeaderName.contentType.description, value: "application/json")
                        try post.content.encode(User.init(id: user.id, name: user.name))
                    }
                })
                return newUser
            }
        }
    }

This controller has two methods. Our find method takes a name property and performs a search on the Users in the database. If it finds a user with the matching name it returns it in the response, if it doesn’t it sends a 404 status code.

The our second method create also take a name property as a parameter and creates a new user. We start by creating our Chatkit end point, you will need to add your instance ID here. We then create a new UUID for our user and then attempt to create and save this user. We can only create the user if the name is unique. If we successfully save the user to our database we then create a JWT token and use this as our authorisation token before we post the details of our user to the Chatkit endpoint to create our user.

Create a JW token

The Chatkit API requires the requests to be signed with a JSON Web Token. We need to create a function that will create this token for us. Fortunately we have installed a package that will help with this, PerfectoCrypto. Let’s create a controller that will handle this, close Xcode and enter the following in terminal.

    $ touch Sources/App/Controllers/AuthController.swift
    $ vapor xcode -y

Open your newly created AuthController.swift and add the following:

    // ../Sources/App/Controllers/AuthController.swift
    import Vapor
    import Foundation
    import PerfectCrypto

    final class AuthController {

        // Creates a JWT token lasting 15 mins
        static func createJWToken() -> String {
            let timeStamp = Int(Date.init().timeIntervalSince1970)
            let tstPayload = ["instance": "YOUR_CHATKIT_INSTANCE_ID",
                              "iss": "api_keys/API_KEY_ID",
                              "exp": timeStamp + (15 * 60),
                              "iat": timeStamp,
                              "sub": "MasterShake",
                              "su":true] as [String : Any]
            let secret = "YOUR_CHATKIT_SECRET KEY"
            guard let jwt1 = JWTCreator(payload: tstPayload) else {
                return ""
            }
            let token = try! jwt1.sign(alg: .hs256, key: secret)
            return token
        }
    }

Replace the placeholders with your instance ID, API key and secret key that we made a note of earlier. This function will use the package we installed in order to create a token that we can add as BearerAuthorization in our requests with the API. The token will last for 15 minutes from creation and importantly the su key gives admin access.

Create routes

Finally we need to create our routes. Open your routes file and replace the contents with the following.

    // ../Sources/App/routes.swift
    import Vapor

    /// Register your application's routes here.
    public func routes(_ router: Router) throws {
        let userController = UserController()
        router.post("api", "users", "new", use: userController.create)
        router.post("api", "users", "login", use: userController.find)
    }

Here we are creating two routes one for creating our new users and one for logging them in. Note we won’t be handling password authentication but just logging users in if we find the username in the database.

We can now run the server and it should start listening on http://localhost:8080

Conclusion

In this part of the tutorial we’ve learnt how to create our Vapor server, create and save a unique user to the database and also interact with the Pusher API directly. In the second part of this tutorial we will create the iOS application that interacts with this server and the Pusher Chatkit SDK.

The source code for part one can be found here.

Clone the project repository
  • Chat
  • iOS
  • Swift
  • Chatkit

Products

  • Channels
  • Beams
  • Chatkit

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.