Build a CI powered RESTful API with Laravel
Fullstack Developer and Tech Author
When it comes to building RESTful APIs, PHP’s open source Laravel framework remains a top 5 backend framework for web development. Laravel also makes testing your API endpoints a breeze by providing an easy-to-use testing suite. In this post, we will build a token-based authentication API with Laravel, write tests for the endpoints, and automate the build and testing process with CircleCI.
Prerequisites
To follow along with this post, you will need a few things:
- PHP >= 8.0.0 installed on your system (you can confirm that your version in high enough by running the command
php -v
on your terminal) - Composer installed globally (confirm this by running the
composer
command on your terminal) - Git installed on your system
- A GitHub account
- A CircleCI account
Our tutorials are platform-agnostic, but use CircleCI as an example. If you don’t have a CircleCI account, sign up for a free one here.
Once you have these up and running, you will be ready to follow along with the tutorial.
Scaffolding the Laravel API
Your first step is to scaffold a new Laravel application. To begin, issue this command:
composer create-project laravel/laravel my-laravel-api-tutorial
This will create a project with the name my-laravel-api-tutorial
in the location where the command was run. You can give your project any name you want.
Setting up the database
For this tutorial, you will use SQLite for both the testing and main databases. In practice, you would use a more sophisticated DBMS like MySQL or MSSQL, but we will keep things in this tutorial simple. The configuration for the main database is in the .env
file. Create a .env.testing
file to hold your test database configuration.
You already have the .env
file that comes with the default project scaffolding. Create a new file named .env.testing
and copy the contents of .env.example
(which is also created by default), into it.
Replace the database configuration section of both files (.env
and .env.testing
) with this configuration:
DB_CONNECTION=sqlite
DB_HOST=null
DB_PORT=null
DB_DATABASE=database/database.sqlite
DB_USERNAME=null
DB_PASSWORD=null
You are using the same configuration for both files because both your main and testing databases use SQLite. In the previous code snippet, your connection is set to sqlite
and your database to database.sqlite
, which is in the database
folder. You have not yet created this database.sqlite
file. Go into the database
folder at the root of your project and create it.
The final task in setting up your database is to use one of Laravel’s helpers to point to your SQLite database file in the database configuration file in config/database.php
. Inside the config/database.php
file, replace the SQLite configuration in the connections
array with this one:
'sqlite' => [
'driver' => 'sqlite',
'database' => database_path('database.sqlite'),
'prefix' => '',
],
This makes sure that the SQLite configuration always points to the correct database path, even if you change the environment in which the application is running.
To confirm that all is working perfectly, migrate the database by running this command:
php artisan migrate
A successful migration should display something similar to the screenshot below:
Make sure that you ignore the database file by declaring it in your .gitignore
file.
Setting up token-based authentication with Passport
Your next task is to set up an API project to use token-based authentication. You will be doing that using the Laravel Passport OAuth library which provides a full OAuth2 server implementation for your Laravel application. First, install the laravel/passport
package:
composer require laravel/passport --with-all-dependencies
Once the installation is done, run this command to run the migrations that come with laravel/passport
:
php artisan migrate
This should print a screen similar to the one below on your command line:
Passport requires encryption keys to generate access tokens, these keys need to be generated and saved in the database. To generate these keys, run this command:
php artisan passport:install
After running this command successfully, you should see your client secrets generated.
The next step is to add the Laravel\Passport\HasApiTokens
trait to your App\User
model. This will introduce helper methods from the laravel/passport
package into the application to help with inspecting a user’s token and scopes. Open the app/Models/User.php
file and replace its contents with the code below:
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
In this file, you imported the Laravel\Passport\HasApiTokens
trait and instructed your class to use it.
Next, open config/auth.php
configuration file and define an api authentication guard to set the driver option to passport as shown below:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
With this, the application will now use Passport’s TokenGuard to authenticate incoming requests.
Creating the API endpoints
You are going to be building a simple user profile API. The user will be able to:
- Sign up for a new account
- Log in to their accounts using their login credentials
- Fetch their profile
- Log out of the application
You need to create API endpoints for these four tasks. Go into the file routes/api.php
and add the following code:
<?php
use App\Http\Controllers\AuthController;
Route::group([
'prefix' => 'auth'
], function () {
Route::post('login', [AuthController::class, 'login']);
Route::post('signup', [AuthController::class, 'signup']);
Route::group([
'middleware' => 'auth:api'
], function() {
Route::get('logout', [AuthController::class, 'logout']);
Route::get('user', [AuthController::class, 'user']);
});
});
This code creates an auth
route group and creates four routes for the four tasks stated earlier.
The auth/logout
and auth/user
routes are authenticated with the auth:api
middleware to ensure that only authenticated access is allowed to these endpoints. You also referenced AuthController
above which is the controller that you will be creating next. To do that, run this command to create the controller:
php artisan make:controller AuthController
Now place this code into the newly created controller:
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AuthController extends Controller
{
/**
* Create user
*
* @param [string] name
* @param [string] email
* @param [string] password
* @param [string] password_confirmation
* @return [string] message
*/
public function signup(Request $request)
{
$request->validate([
'name' => 'required|string',
'email' => 'required|string|email|unique:users',
'password' => 'required|string|confirmed'
]);
$user = new User([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password)
]);
$user->save();
return response()->json([
'message' => 'Successfully created user!'
], 201);
}
/**
* Login user and create token
*
* @param [string] email
* @param [string] password
* @param [boolean] remember_me
* @return [string] access_token
* @return [string] token_type
* @return [string] expires_at
*/
public function login(Request $request)
{
$request->validate([
'email' => 'required|string|email',
'password' => 'required|string',
'remember_me' => 'boolean'
]);
$credentials = request(['email', 'password']);
if(!Auth::attempt($credentials)){
return response()->json([
'message' => 'Unauthorized'
], 401);
}
$user = $request->user();
$tokenResult = $user->createToken('Personal Access Token')->accessToken;
if ($request->remember_me){
$tokenResult->expires_at = Carbon::now()->addWeeks(1);
}
$tokenResult->save();
return response()->json([
'access_token' => $tokenResult,
'token_type' => 'Bearer',
'expires_at' => Carbon::parse(
$tokenResult->expires_at
)->toDateTimeString()
]);
}
/**
* Logout user (Revoke the token)
*
* @return [string] message
*/
public function logout(Request $request)
{
$request->user()->token()->revoke();
return response()->json([
'message' => 'Successfully logged out'
]);
}
/**
* Get the authenticated User
*
* @return [json] user object
*/
public function user(Request $request)
{
return response()->json($request->user());
}
}
That is a lot of code. I will go through it for you, method by method.
The signup method
This method receives an email, a password, and the user’s name as parameters, it then creates a user using the Lucid model’s save()
method on the User
model and returns a success message along with a status of 201.
The login method
This method receives the user email and password and an optional remember_me
parameter. It then verifies the credentials, and upon successful verification, returns the access token, token type, and the time that the token will expire.
The logout method
This method receives and revokes the access token given to the user then sends a success message upon log out.
The user method
This method receives the user’s access token and uses it to return the user’s details.
Note: If you see a file .rnd
created in your project, ignore this file by adding it to your .gitignore
file.
Testing the API endpoints
It is now time to write some tests for the API endpoints. Create a test by running this command:
php artisan make:test UserTest
This creates the UserTest
test file inside the tests/Feature
folder. Delete the ExampleTest.php
file that comes by default in the folder. Open the UserTest.php
file just created and replace its contents with the code below:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\User;
class UserTest extends TestCase
{
use WithFaker;
private $password = "mypassword";
public function testUserCreation()
{
$name = $this->faker->name();
$email = $this->faker->email();
$response = $this->postJson('/api/auth/signup', [
'name' => $name,
'email' => $email,
'password' => $this->password,
'password_confirmation' => $this->password
]);
$response
->assertStatus(201)
->assertExactJson([
'message' => "Successfully created user!",
]);
}
public function testUserLogin()
{
$name = $this->faker->name();
$email = $this->faker->email();
$user = new User([
'name' => $name,
'email' => $email,
'password' => bcrypt($this->password)
]);
$user->save();
$response = $this->postJson('/api/auth/login', [
'email' => $email,
'password' => $this->password
]);
$response->assertStatus(200);
$this->assertAuthenticated();
}
}
This file contains the test for/api/auth/signup
and /api/auth/login
API endpoints. These endpoints ensure that users are able to sign up and login successfully. The test generated a random email address and user’s name using the already-installed PHP Faker library along with a generic password to create a new user account. The test also asserts that the appropriate HTTP status codes and messages are defined within the controller.
Running tests locally
Now to take your test for a spin. You will be using PHPUnit to run your test. An installation of PHPUnit comes with the phpunit
CLI command. You can have this command running globally with a global installation of PHPUnit, or have it at project level. The default scaffolded Laravel project already has the package installed locally which enables us to run your tests.
To run the tests, run this command at the project root:
php artisan test
This runs the phpunit
command using your local installation of the package. If everything goes fine, all your tests should run successfully and you should have a screen similar to the one below.
Automating your tests with CircleCI
Time to introduce the power of CI/CD into your Laravel API. You will be creating a pipeline that runs tests automatically every time you push new code. You get either a successful or failed CI/CD pipeline status from CircleCI. Your pipeline will consist of these steps:
- Spinning up the required environment
- Installing dependencies with composer
- Setting up a
.env
environment file for the project - Setting up the test database and running migrations
- Generating encryption keys for the
laravel/passport
package - Running the tests
To create a CI/CD pipeline, you need to write a configuration file for CircleCI. Go into the root of your project and create a folder named .circleci
. Inside this folder, create a file named config.yml
.
Open the newly created configuration file and use this code for it:
version: 2.1
jobs:
build:
working_directory: ~/repo
docker:
# Specify the version you desire here
- image: cimg/php:8.2.7
steps:
- checkout
- run:
name: "Create Environment file and generate app key"
command: |
mv .env.testing .env
# Download and cache dependencies
- restore_cache:
keys:
# "composer.lock" can be used if it is committed to the repo
- v1-dependencies-{{ checksum "composer.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run:
name: "Install Dependencies"
command: composer install -n --prefer-dist
- save_cache:
key: v1-dependencies-{{ checksum "composer.json" }}
paths:
- ./vendor
# prepare the database
- run:
name: "Generate App key"
command: php artisan key:generate
- run:
name: "Install sqlite"
command: sudo apt-get install libsqlite3-dev
- run:
name: "Create database and run migration"
command: |
touch database/database.sqlite
php artisan migrate --env=testing
- run:
name: "Generate Passport encryption keys"
command: php artisan passport:install
# run tests with phpunit
- run:
name: "Run Tests"
command: php artisan test
In this configuration file, you pulled the Docker image for PHP from the CircleCI image registry. This image contains the PHP programming language, composer, Node and browsers extensions.
Next, it checked out your code, created a .env
file and installed the project’s dependencies. After generating the application key with php artisan key:generate
, it installed SQLite, ran migrations for the database and finally ran the tests with PHPUnit.
You can now set up a repository on GitHub and push your project to it. Review Pushing a project to GitHub for instructions. In the next section, you will link the project to CircleCI.
Connecting the API project to CircleCI
Now its time to hand over this configuration to CircleCI to set up your pipeline. You do that by connecting your project to CircleCI. Head over to your CircleCI dashboard. If you signed up with your GitHub account, all your repositories will be available on your project’s dashboard.
Click Set Up Project next to your my-laravel-api-tutorial
project.
On the Select your config.yml file screen, select the Fastest option and type main
as the branch name. CircleCI will automatically locate the config.yml
file. Click Set Up Project to start the workflow.
CircleCI will begin to run your pipeline configuration. If you have followed the instructions correctly, you should have a successful build indicated by the screen below.
Perfect. You have successfully powered your Laravel API with a CI/CD pipeline that automatically runs your test using CircleCI.
Conclusion
In this article, you developed a token-based authentication API with Laravel, tested it, and automated the building and testing process using a CI/CD pipeline created for and run by CircleCI. This is just the starting point for bringing automation into the development of your Laravel APIs. There is a lot more you can do with CircleCI to create a more robust and complex pipeline that takes away a lot of the manual tasks involved in application development.
I hope you have learned something valuable from this post. The complete source code can be found here on GitHub.
Happy coding!