Build an iOS chat app using Swift and Chatkit - Part 2: Creating our chat API backend
To follow this series you will need Xcode, Cocoapods, PHP and Laravel installed on your machine. Some knowledge of Xcode and Swift will be helpful.
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:
CHATKIT_INSTANCE_LOCATOR="INSTANCE_LOCATOR_HERE"
CHATKIT_SECRET_KEY="SECRET_KEY_HERE"
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:
'chatkit' => [
'instanceLocator' => env('CHATKIT_INSTANCE_LOCATOR'),
'secret' => env('CHATKIT_SECRET_KEY'),
],
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:
$ php artisan make:model Room -m
$ 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:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateRoomsTable extends Migration
{
public function up()
{
Schema::create('rooms', function (Blueprint $table) {
$table->unsignedInteger('id')->unique();
$table->string('name');
$table->string('created_by_id');
$table->boolean('private')->default(true);
$table->string('created_at');
$table->string('updated_at');
});
}
public function down()
{
Schema::dropIfExists('rooms');
}
}
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:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateContactsTable extends Migration
{
public function up()
{
Schema::create('contacts', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user1_id');
$table->unsignedInteger('user2_id');
$table->int('room_id')->unique();
$table->timestamps();
$table->foreign('room_id')->references('id')->on('rooms');
$table->foreign('user1_id')->references('id')->on('users');
$table->foreign('user2_id')->references('id')->on('users');
});
}
public function down()
{
Schema::dropIfExists('contacts');
}
}
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:
<?php
namespace App;
class Contact extends \Illuminate\Database\Eloquent\Model
{
protected $fillable = ['user1_id', 'user2_id', 'room_id'];
protected $with = ['user1', 'user2', 'room'];
public function user1()
{
return $this->belongsTo(User::class);
}
public function user2()
{
return $this->belongsTo(User::class);
}
public function room()
{
return $this->belongsTo(Room::class);
}
public function scopeFor($query, $user_id)
{
return $query->where('user1_id', $user_id)->orWhere('user2_id', $user_id);
}
}
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:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Room extends Model
{
public $timestamps = false;
public $incrementing = false;
protected $casts = ['private' => 'boolean'];
protected $fillable = [
'id', 'name', 'created_by_id', 'private', 'created_at', 'updated_at'
];
}
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:
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
protected $fillable = ['name', 'email', 'password', 'chatkit_id'];
protected $hidden = ['password', 'remember_token'];
public function setPasswordAttribute($value)
{
$this->attributes['password'] = bcrypt($value);
}
}
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:
<?php
namespace App;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
protected $fillable = ['name', 'email', 'password', 'chatkit_id'];
protected $hidden = ['password', 'remember_token'];
public function setPasswordAttribute($value)
{
$this->attributes['password'] = bcrypt($value);
}
}
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:
'guards' => [
// ...
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
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:
Encryption keys generated successfully.
Personal access client created successfully.
Client ID: 1
Client Secret: N6GH0MyTCIBW89g7BkzZwc6Q3gcPU16p91G7LDLv
Password grant client created successfully.
Client ID: 2
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:
<?php
namespace App;
use Chatkit\Chatkit as PusherChatkit;
class Chatkit extends PusherChatkit
{
}
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:
$this->app->singleton('App\Chatkit', function () {
$instanceLocator = config('services.chatkit.instanceLocator');
$secret = config('services.chatkit.secret');
return new \App\Chatkit([
'instance_locator' => $instanceLocator,
'secret' => $secret
);
});
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:
<?php
namespace App\Http\Controllers;
use App\User;
use App\Chatkit;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function create(Request $request, Chatkit $chatkit)
{
$data = $request->validate([
'name' => 'required|string|max:255',
'password' => 'required|string|min:6',
'email' => 'required|string|email|max:255|unique:users',
]);
$data['chatkit_id'] = str_slug($data['email'], '_');
$response = $chatkit->createUser([
'id' => $data['chatkit_id'],
'name' => $data['name']
);
if ($response['status'] !== 201) {
return response()->json(['status' => 'error'], 400);
}
return response()->json(User::create($data));
}
}
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:
Route::get('/contacts', 'ContactController@index')->middleware('auth:api');
Route::post('/contacts', 'ContactController@create')->middleware('auth:api');
Next open the app/Http/Controllers/ContactController.php
and paste the code below into the file:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\{Room, User, Contact, Chatkit};
class ContactController extends Controller
{
public function index()
{
$contacts = [];
$me = request()->user();
// Loop through the contacts and format each one
Contact::for($me->id)->get()->each(function ($contact) use ($me, &$contacts) {
$friend = $contact->user1_id == $me->id ? $contact->user2:$contact->user1;
$contacts[] = $friend->toArray() + ['room' => $contact->room->toArray()];
});
return response()->json($contacts);
}
public function create(Request $request, Chatkit $chatkit)
{
$user = $request->user();
$data = $request->validate([
'user_id' => "required|not_in:{$user->email}|valid_contact"
]);
$friend = User::whereEmail($data['user_id'])->first();
$response = $chatkit->createRoom([
'creator_id' => env('CHATKIT_USER_ID'),
'private' => true,
'name' => $this->generate_room_id($user, $friend),
'user_ids' => [$user->chatkit_id, $friend->chatkit_id],
]);
if ($response['status'] !== 201) {
return response()->json(['status' => 'error'], 400);
}
$room = Room::create($response['body']);
$contact = Contact::create([
'user1_id' => $user->id,
'user2_id' => $friend->id,
'room_id' => $room->id
]);
return response()->json($friend->toArray() + [
'room' => $contact->room->toArray()
]);
}
private function generate_room_id(User $user, User $user2) : string
{
$chatkit_ids = [$user->chatkit_id, $user2->chatkit_id];
sort($chatkit_ids);
return md5(implode('', $chatkit_ids));
}
}
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:
<?php
namespace App\Http\Controllers;
use App\Chatkit;
use Illuminate\Support\Facades\Auth;
class ChatkitController extends Controller
{
public function getToken(Chatkit $chatkit)
{
$auth_data = $chatkit->authenticate([
'user_id' => Auth::user()->chatkit_id
]);
return response()->json(
$auth_data['body'],
$auth_data['status']
);
}
}
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.
⚠️ 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.
5 June 2018
by Neo Ighodaro