Build an iOS chat app using Swift and Chatkit - Part 2: Creating our chat API backend

Introduction

IMPORTANT:
ChatKit has been retired from the Pusher product stack as of April, 2020. This tutorial will no longer be functional in its current version. Explore our other Pusher tutorials to find out how Channels and Beams can help you build.

In the first part of this series, we considered how to prototype our application, how to install and use Clean Swift templates to structure our application and how Clean Swift Architecture works. In this part we will be considering how to build our application’s API backend using PHP and Laravel.

When building applications it is sometimes necessary to have an API. The API will be a central datapoint, which both our mobile application and web applications can connect to and implement as necessary. Let’s go ahead and see what we expect our API to be able to do.

What will the API do?

Based on our prototype, we have a few workflows that require some data. The API will help us provide that data, validate the user, and more.

Here is what we expect the API to be able to accomplish when we are done:

  • Create new users.
  • Authenticate users using email and password.
  • Provide OAuth2 tokens to authenticated users.
  • Use OAuth2 token to limit access to functionalities that are only for authenticated users.
  • Allow users to add new contacts.
  • Provide the contacts list for a user.
  • Provide a Chatkit token so the application can connect directly to Chatkit.

Requirements

To follow along in this part of this article, you will need the following:

  • Knowledge of PHP and Laravel.
  • PHP >= 7.0.0 installed locally.
  • Laravel CLI installed locally.
  • A Chatkit application.
  • Postman installed on your machine.

When you have all the requirements, lets get started.

Creating our backend API using Laravel

Let us start creating our backend API by first setting up our project.

Setting up our environment

To start, open your terminal application and run the command below to create a new Laravel application.

    $ laravel new chatapi

This will create a new Laravel application in the chatapi directory. This will be our API project workspace. Next, you’ll need to create a MySQL database on your local machine. We will need the database for our API to store and retrieve data.

💡 You can use any database engine you feel comfortable with. You just need to edit your .env file to connect to the database.

Next open the .env file and update the DB_DATABASE value to the name of your database. Also, update the DB_USERNAME and DB_PASSWORD to your database username and password.

Paste these new key values below at the bottom of the .env file:

1CHATKIT_INSTANCE_LOCATOR="INSTANCE_LOCATOR_HERE"
2    CHATKIT_SECRET_KEY="SECRET_KEY_HERE"
3    CHATKIT_USER_ID="USER_ID_HERE"

We will need these values to connect to Chatkit. You'll need to replace the placeholder values with the credentials from your Chatkit dashboard.

To get a value for the CHATKIT_USER_ID you need to go to the inspector tab of your Chatkit dashboard and create a user. Whatever user ID you use there is the ID you should set as the CHATKIT_USER_ID env var.

To make sure our application can use these credentials, open the config/services.php file and in there add the snippet below to the array of services:

1'chatkit' => [
2        'instanceLocator' => env('CHATKIT_INSTANCE_LOCATOR'),
3        'secret' => env('CHATKIT_SECRET_KEY'),
4    ],

Creating and updating migrations and models

The next thing we want to do is update our existing migrations and create new ones. Open the create_users_table migration in the databases/migrations directory and in the up method, add the line of code below to add a new column in the users table:

    $table->string('chatkit_id')->unique();

💡 We need the chatkit_id column to store the ID of the user on the Chatkit servers. This will make it easy for us to link a user on our database to a user on the Chatkit database.

Next, let’s create a few migrations. Open a terminal and run the command below to create a new migration, controller and model in one command:

1$ php artisan make:model Room -m
2    $ php artisan make:model Contact -mc

The -mc flag in the command stands for migration and controller. This means that when the model is being created, it will create an accompanying controller and migration.

Open the create_rooms_table migration file and replace the contents with the code below:

1<?php
2
3    use Illuminate\Support\Facades\Schema;
4    use Illuminate\Database\Schema\Blueprint;
5    use Illuminate\Database\Migrations\Migration;
6
7    class CreateRoomsTable extends Migration
8    {
9        public function up()
10        {
11            Schema::create('rooms', function (Blueprint $table) {
12                $table->unsignedInteger('id')->unique();
13                $table->string('name');
14                $table->string('created_by_id');
15                $table->boolean('private')->default(true);
16                $table->string('created_at');
17                $table->string('updated_at');
18            });
19        }
20
21        public function down()
22        {
23            Schema::dropIfExists('rooms');
24        }
25    }

The migration above will create a data structure is the same as the response Chatkit returns when rooms are created.

Next, open the create_contacts_table migration file and replace the contents with the code below:

1<?php
2
3    use Illuminate\Support\Facades\Schema;
4    use Illuminate\Database\Schema\Blueprint;
5    use Illuminate\Database\Migrations\Migration;
6
7    class CreateContactsTable extends Migration
8    {
9        public function up()
10        {
11            Schema::create('contacts', function (Blueprint $table) {
12                $table->increments('id');
13                $table->unsignedInteger('user1_id');
14                $table->unsignedInteger('user2_id');
15                $table->int('room_id')->unique();
16                $table->timestamps();
17                $table->foreign('room_id')->references('id')->on('rooms');
18                $table->foreign('user1_id')->references('id')->on('users');
19                $table->foreign('user2_id')->references('id')->on('users');
20            });
21        }
22
23        public function down()
24        {
25            Schema::dropIfExists('contacts');
26        }
27    }

In the migration above we have created the contacts table with the columns user1_id, user2_id, room_id, created_at and updated_at.

The user1_id and user2_id will be unique columns and will be foreign keys to the user table. When user1 sends a request to add a new contact user2 to the API, the room will be created on Chatkit for both users, then the relationship will be saved to the database along with the room_id returned from Chatkit when the room was created.

Now run the command below to perform database migrations:

    $ php artisan migrate 

Next let us update our models so that they support the migration we just created. Open the app/Contact.php file and paste the code below into it:

1<?php
2    namespace App;
3
4    class Contact extends \Illuminate\Database\Eloquent\Model
5    {
6        protected $fillable = ['user1_id', 'user2_id', 'room_id'];
7
8        protected $with = ['user1', 'user2', 'room'];
9
10        public function user1()
11        {
12            return $this->belongsTo(User::class);
13        }
14
15        public function user2()
16        {
17            return $this->belongsTo(User::class);
18        }
19
20        public function room()
21        {
22            return $this->belongsTo(Room::class);
23        }
24
25        public function scopeFor($query, $user_id)
26        {
27            return $query->where('user1_id', $user_id)->orWhere('user2_id', $user_id);
28        }
29    }

The model above is pretty self-explanatory. The user1, user2, and room methods just define relationships while the scopeFor method is an Eloquent scope that creates query builder shortcuts.

Open the app/Room.php model so we can update it to work with out rooms database. Paste the code below into the file:

1<?php
2    namespace App;
3
4    use Illuminate\Database\Eloquent\Model;
5
6    class Room extends Model
7    {
8        public $timestamps = false;
9
10        public $incrementing = false;
11
12        protected $casts = ['private' => 'boolean'];
13
14        protected $fillable = [
15            'id', 'name', 'created_by_id', 'private', 'created_at', 'updated_at'
16        ];
17    }

In the model above we set $timestamps to false so Eloquent does not try to manage the database timestamps automatically. We set $incrementing to false so Eloquent does not try to manage the incrementing of id, the primary key.

Next open the app/User.php model and replace the code with the code below:

1<?php
2    namespace App;
3
4    use Illuminate\Notifications\Notifiable;
5    use Illuminate\Foundation\Auth\User as Authenticatable;
6
7    class User extends Authenticatable
8    {
9        use Notifiable;
10
11        protected $fillable = ['name', 'email', 'password', 'chatkit_id'];
12
13        protected $hidden = ['password', 'remember_token'];
14
15        public function setPasswordAttribute($value)
16        {
17            $this->attributes['password'] = bcrypt($value);
18        }
19    }

In the user model we have the setPasswordAttribute which is an Eloquent mutator for the password property.

Great, now let’s install Laravel Passport.

Installing Laravel Passport

To install Laravel Passport you need to use Composer to pull in the package. Run the command below in your terminal to install the package using Composer:

    $ composer require laravel/passport

Once the installation is complete, run the command below to perform the passport migration:

    $ php artisan migrate

After running the command, add the Laravel\Passport\HasApiTokens trait to your App\User model as seen below:

1<?php
2    namespace App;
3
4    use Laravel\Passport\HasApiTokens;
5    use Illuminate\Notifications\Notifiable;
6    use Illuminate\Foundation\Auth\User as Authenticatable;
7
8    class User extends Authenticatable
9    {
10        use HasApiTokens, Notifiable;
11
12        protected $fillable = ['name', 'email', 'password', 'chatkit_id'];
13
14        protected $hidden = ['password', 'remember_token'];
15
16        public function setPasswordAttribute($value)
17        {
18            $this->attributes['password'] = bcrypt($value);
19        }
20    }

Next, we need to register the Passport routes to our application. Open the app/providers/AuthServiceProvider.php file and in the boot method add this call:

    Passport::routes();

Finally, open the config/auth.php file and change the driver option of the api authentication guard to passport from token. This forces your application to use Passport’s TokenG``uard when authenticating incoming API requests:

1'guards' => [
2        // ...
3        'api' => [
4            'driver' => 'passport',
5            'provider' => 'users',
6        ],
7    ],

To finish the installation, run the command below:

    $ php artisan passport:install

This will generate encryption keys for generating tokens and also create two clients for your usage. We will be using only the password grant client though. The output of our command will look similar to this:

1Encryption keys generated successfully.
2    Personal access client created successfully.
3    Client ID: 1
4    Client Secret: N6GH0MyTCIBW89g7BkzZwc6Q3gcPU16p91G7LDLv
5    Password grant client created successfully.
6    Client ID: 2
7    Client Secret: nneBZLH70o0Ez9rtpOYCBOzbarrcYpDVLCjnUTdn

💡 Please note the details (Client ID and Client Secret) for the password grant client. You’ll need the credentials in our iOS application when we need to generate tokens to make calls to our API.

To learn more about creating OAuth servers using Laravel and Passport, read this article.

Installing the Pusher Chatkit SDK

The next thing we need to do is install the Chatkit PHP SDK. We will do this using Composer. Run the command below to pull in the PHP SDK:

    $ composer require pusher/pusher-chatkit-server 

Next, create a new file in the app directory, Chatkit.php, and paste in the following:

1<?php
2    namespace App;
3
4    use Chatkit\Chatkit as PusherChatkit;
5
6    class Chatkit extends PusherChatkit
7    {
8    }

In the class above, we have extended the Chatkit PHP SDK main class Chatkit\Chatkit. All we’re really using this for in our app is to help us create a Chatkit singleton, as you’ll see next.

Next, open app/providers/AppServiceProvider.php and paste the following code inside the register method:

1$this->app->singleton('App\Chatkit', function () {
2        $instanceLocator = config('services.chatkit.instanceLocator');
3        $secret = config('services.chatkit.secret');
4
5        return new \App\Chatkit([
6            'instance_locator' => $instanceLocator, 
7            'secret' => $secret
8        );
9    });

This will register the App\Chatkit class into Laravel’s IoC container and make sure every time Laravel attempts to resolve the class using IoC, the class will return an instantiated and configured instance of App\Chatkit. This will be useful when we inject the class into our controllers later.

Creating our endpoints

Now that we have installed the Chatkit PHP SDK and Laravel Passport, let’s go on to create our endpoints.

  • An endpoint that handles login and provides a token for making authenticated calls to your backend API.
  • An endpoint that handles sign up.
  • An endpoint that returns all contacts for an authenticated user.
  • An endpoint that handles adding a new contact.
  • An endpoint that returns the Chatkit token for the authenticated user. The Chatkit token can be used to make calls directly using the Swift Chatkit SDK.

Let’s get started.

The login endpoint

We do not have to create the endpoint for handling login as Laravel Passport handles that for us. When we registered the Laravel Passport routes in AuthServiceProvider we registered some routes to our application.

The one we are interested in is /oauth/token. When you send a POST request to the /oauth/token endpoint with the request body: username, password, grant_type, client_id and client_secret, we will get a token back for the user.

The signup endpoint

The next endpoint on our list is the sign up endpoint. Open the routes/api.php file and add the route definition below to the file:

    Route::post('/users/signup', 'UserController@create');

Next, let’s create the controller and the method that will respond to the route. Create a new file in the app/Http/Controllers directory, UserController.php and paste the code below into the file:

1<?php
2    namespace App\Http\Controllers;
3
4    use App\User;
5    use App\Chatkit;
6    use Illuminate\Http\Request;
7
8    class UserController extends Controller
9    {
10        public function create(Request $request, Chatkit $chatkit)
11        {
12            $data = $request->validate([
13                'name' => 'required|string|max:255',
14                'password' => 'required|string|min:6',
15                'email' => 'required|string|email|max:255|unique:users',
16            ]);
17
18            $data['chatkit_id'] = str_slug($data['email'], '_');
19
20            $response = $chatkit->createUser([
21                'id' => $data['chatkit_id'], 
22                'name' => $data['name']
23            );
24
25            if ($response['status'] !== 201) {
26                return response()->json(['status' => 'error'], 400);
27            }
28
29            return response()->json(User::create($data));
30        }
31    }

In the create method, we validate the request data sent, it expects name, email and password, then when the validation passes, we generate a chatkit_id and create a new Chatkit user using the Chatkit PHP SDK. If it succeeds we create the user locally and save the chatkit_id so we can pull the associated user from Chatkit anytime we wish.

That’s all for that endpoint.

The contacts endpoint

The next endpoint on our list is the list contacts and add contacts endpoint. Open the routes/api.php file and add the route definition below to the file:

1Route::get('/contacts', 'ContactController@index')->middleware('auth:api');
2    Route::post('/contacts', 'ContactController@create')->middleware('auth:api');

Next open the app/Http/Controllers/ContactController.php and paste the code below into the file:

1<?php
2
3    namespace App\Http\Controllers;
4
5    use Illuminate\Http\Request;
6    use App\{Room, User, Contact, Chatkit};
7
8    class ContactController extends Controller
9    {
10        public function index()
11        {
12            $contacts = [];
13            $me = request()->user();
14
15            // Loop through the contacts and format each one
16            Contact::for($me->id)->get()->each(function ($contact) use ($me, &$contacts) {
17                $friend = $contact->user1_id == $me->id ? $contact->user2:$contact->user1;
18                $contacts[] = $friend->toArray() + ['room' => $contact->room->toArray()];
19            });
20
21            return response()->json($contacts);
22        }
23
24        public function create(Request $request, Chatkit $chatkit)
25        {
26            $user = $request->user();
27
28            $data = $request->validate([
29                'user_id' => "required|not_in:{$user->email}|valid_contact"
30            ]);
31
32            $friend = User::whereEmail($data['user_id'])->first();
33
34            $response = $chatkit->createRoom([
35                'creator_id' => env('CHATKIT_USER_ID'),
36                'private' => true,
37                'name' => $this->generate_room_id($user, $friend),
38                'user_ids' => [$user->chatkit_id, $friend->chatkit_id],
39            ]);
40
41            if ($response['status'] !== 201) {
42                return response()->json(['status' => 'error'], 400);
43            }
44
45            $room = Room::create($response['body']);
46
47            $contact = Contact::create([
48                'user1_id' => $user->id,
49                'user2_id' => $friend->id,
50                'room_id' => $room->id
51            ]);
52
53            return response()->json($friend->toArray() + [
54                'room' => $contact->room->toArray()
55            ]);
56        }
57
58        private function generate_room_id(User $user, User $user2) : string
59        {
60            $chatkit_ids = [$user->chatkit_id, $user2->chatkit_id];
61            sort($chatkit_ids);
62            return md5(implode('', $chatkit_ids));
63        }
64    }

In the ContactController above we have three methods, index, create, and generate_room_id

In the index method, we get all the contacts of the user and loop through each contact and append the contact to the contacts array. Finally we return the contacts as the response.

In the create method we validate the request user_id (the ID of the contact to add) and then we create a room for both users on Chatkit. If the room creation is successful, we then create the contact connection in the database and return the formatted contact.

In the generate_room_id method, we just generate a room ID using the chatkit_id of both users and run that through md5.

The Chatkit token endpoint

The last endpoint we need to create is the endpoint that generates and returns a Chatkit token. This token will then be used by the iOS application to communicate directly with Chatkit if needed.

In the routes file add the route below:

    Route::post('/chatkit/token', 'ChatkitController@getToken')->middleware('auth:api');

Next, create a new ChatkitController.php in the app/Http/Controllers directory and paste the code below into the file:

1<?php
2    namespace App\Http\Controllers;
3
4    use App\Chatkit;
5    use Illuminate\Support\Facades\Auth;
6
7    class ChatkitController extends Controller
8    {
9        public function getToken(Chatkit $chatkit)
10        {
11            $auth_data = $chatkit->authenticate([
12                'user_id' => Auth::user()->chatkit_id
13            ]);
14
15            return response()->json(
16                $auth_data['body'],
17                $auth_data['status']
18            );
19        }
20    }

In the controller we have the getToken method. This method uses the authenticate method of the Chatkit SDK to generate our access token that will then be used in the application to make requests to Chatkit directly.

This is the final endpoint we have to create.

Testing our endpoints

To test that our endpoints work we will first need to start a PHP server. In your terminal, cd to the root of your API project and run the command below:

    $ php artisan serve

Now you can visit the URL http://127.0.0.1:8000 and you should see the default Laravel page.

Now fire up Postman. Download and import this collection to Postman. When you have imported the collection you can now make requests to your API to test the responses you will receive.

Create-iOS-Chat-App-Using-Chatkit-postman

⚠️ You will need to edit the “Body” of the “Fetch OAuth Token” endpoint. In the Client ID and Client Secret have to match the Laravel Passport credentials you already have. Also, to make authenticated calls to the other endpoints you need to get the access_token from the “Fetch OAuth Token” call and then add it to the “Authorization” header of the endpoint you want to call.

Conclusion

In this part we were able to create the backend for our iOS chat application. The backend was created using Laravel and PHP. Hopefully you picked up some things from this part.

In the next part of this series, we will focus on creating the application itself. We will add the Swift logic and also we will build out most of the chat applications features.

If you have any questions or feedback about the article, please leave a comment below. The source code to the application built in this series is available on GitHub.

IMPORTANT:
ChatKit has been retired from the Pusher product stack as of April, 2020. This tutorial will no longer be functional in its current version. Explore our other Pusher tutorials to find out how Channels and Beams can help you build.