HOW TO: Create Lumen API – Basic

This blog post is going to show step by step how to build a very simple API using the Lumen Framework. The API will allow users to create, read, update and delete data. The source code for this project is available on GitHub.

Content:

Dependencies

The dependencies above must be installed on your machine for the Lumen project to work.

You will need to follow separate installing instructions, depending on your OS. Ensure that PHP and Composer are installed correctly in your system.

For the purposes of this tutorial, I will develop this project in Manjaro, a Linux OS. If you are developing your application on Windows or other OS you could consider using Vagrant with Laravel Homestead or another VM (virtual machine). If you would like me to make a step by step tutorial (video or blog post) on how to use Vagrant please let me know in the comments.

What is CRUD?

CRUD is the acronym for Create, Read, Update, and Delete, these are the four basic functions that models should at least be able to do.

When developing APIs, models should be able to Create, Read, Update and Delete resources. CRUD is a commonly used paradigm when building web applications, as it allows developers to easily remember the basic structure every model should follow.

What is Lumen?

This a PHP based micro-framework that is a “smaller, faster, leaner version of a full web application framework”, Laravel in this case. Lumen is used to quickly build small web applications, APIs. Read more about it here.

What is Composer?

Composer is a tool for dependency management in PHP. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you.

Composer Docs

In simple terms, composer allows you to install packages at a project level really easily. For example, imagine you have two projects. Project One needs to use a third party package that deals with error handling, Project Two doesn’t need that package, using composer you can install the error handling package just for Project One. 

Create a Lumen Project

Start new project

There are two really easy ways to create new Lumen Projects:

  • Using the Lumen Installer
  • Using Composer create-project

I tend to use Composer most of the times as I always have it installed and I find it easy. This page shows you how to use both ways. In this tutorial, I will be using Composer.

In your terminal, in the folder where you want to keep your project files, run the command below to start a new lumen project (replace your-project-name with the name of your project):

composer create-project --prefer-dist laravel/lumen your-project-name
composer create-project command
Run composer create-project command in console

When the install is done, go and have a look inside the project folder. In my case is in /Work/basic-api folder. Open the folder in your preferred code editor. I will be using Visual Studio Code.

There you should see your project files, same structure as the one from the picture below:

API Lumen Project Structure
Lumen API Project Structure in VSCode

In this tutorial I will only address the process of creating a simple API using Lumen, therefore I will not be discussing the project folder structure. If you would like a more in-depth tutorial please let me know in the comments.

Test the installation worked

Open the terminal in VSCode and run the command below:

php -S localhost:8000 -t public
Start API locally
Start local php server to test API

Open http://localhost:8000 in your browser, if a page like the one from the picture below loads then it means that the project is installed and works correctly so far.

API runs locally
Lumen application opened in browser

Use SQLite for local development

APIs are usually connected to databases, for local development I use SQLite as it’s quick and flexible and it offers all the functionality you need.

First, in the /database folder, create a new .sqlite file called database.sqlite. This file will store our data locally but leave it empty for now.

Second, update your .env file to use sqlite, you can delete the .env.example file now but please read more about the env file configurations here. Use the code below as an example for now:

APP_NAME=Lumen
APP_ENV=local
APP_KEY=generateYourOwn
APP_DEBUG=true
APP_URL=http://localhost
APP_TIMEZONE=UTC

LOG_CHANNEL=stack
LOG_SLACK_WEBHOOK_URL=

DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=/full/path/to/your/api/database/database.sqlite

CACHE_DRIVER=file
QUEUE_CONNECTION=sync

Generate an APP_KEY by running the following command in your terminal and replacing generateYourOwn in the .env file:

php -r "require 'vendor/autoload.php'; echo str_random(32).PHP_EOL;"

Create migration files

The migrations files are dictating the database structure. We create them using php artisan. Please read more about it here. For this basic API, we will have a simple table called 2019_06_26_204555__create_players_table.php. This file will be stored in /database/migrations and will be created automatically when we ran the following command:

php artisan make:migration create_players_table --create=players

NOTE: If you would like a more in-depth tutorial of how to work with the migrations & seeding, please let me know in the comments.

We now have the migration created and it contains a basic function that creates our table with three columns, id, created_at, and updated_at. We will update the code to add a few more columns to the table.

Lumen API migration file
Example Lumen Migration File

Create Seeding Files

Seeding files are also created automatically and they follow this naming convention: TableNameTableSeeder so in this project’s case, the name of the file will be PlayersTableSeeder.php. This again, will be created automatically using the following php artisan command:

php artisan make:seeder PlayersTableSeeder
Lumen API Seeding File Example
Lumen Seeding File example – adds two entries to players table

Lastly, don’t forget to update DatabaseSeeder.php. This is the file that calls all of your seeder classes and makes sure that your tables are seeded.

$this->call('PlayersTableSeeder');

Now is time to migrate and seed our local database, again using php artisan. Run the following command in your terminal:

php artisan migrate --seed

If the command ran without any errors, you can now open your database.sqlite file and see that is not empty anymore. To see the data in a more readable format, you can use an extension like SQLite, is free to install and easy to use.

Lumen API local data view
SQLITE Extension view

Now that the local database contains some data we can play with, is time to build the API endpoints.

Create Route Endpoints

Open web.php located in the /routes directory. Here you will see an example route that basically says “every time you hit the main route ‘/’ return the app version”. We want to add another route that will link our functions from the controller files to the endpoints.

The code below groups all the endpoints together under one prefix, meaning that to get to the endpoint /players the URL now must be http://localhost:8000/api/v1/players instead of just http://localhost:8000/players. Ideally, you would version your APIs from the beginning, this way you will be able to add more features and change the API in version 2, without causing any disruptions to clients that are still using version 1.

$router->group([
    'prefix' => 'api/v1',
], function () use ($router){
    $router->get('/players', 'PlayersController@index');
    $router->get('/players/{id}', 'PlayersController@show');
    $router->post('/players', 'PlayersController@store');
    $router->patch('/players/{id}', 'PlayersController@update');
    $router->delete('/players/{id}', 'PlayersController@destroy');
});

As you can see, we cover all the CRUD functionality:

  • /players – get all the players
  • /players/{id} – get a specific player
  • /players – create a new player
  • /players/{id} – update a player
  • /players/{id} – delete a player

Route syntax explained: $router->get(‘/players’, ‘PlayersController@index’);

$router->HTTP Request Method Type (‘/the/endpoint/you/want‘, ‘FileNameController@theFunctionYouWantToCall‘)

At the moment the routes are pointing to a file called PlayersController. This file does not exist yet, so let’s go and create it in /app/Http/Controllers directory. The code below contains all the functions we need for our routes, each function returning a different string for now.

<?php

namespace App\Http\Controllers;

class PlayersController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    public function index() {
       
        return "Hello From Index";
    }

    public function show() {
       
        return "Hello From Show";
    }
    
    public function store() {
       
        return "Hello From Store";
    }

    public function update() {
       
        return "Hello From Update";
    }

    public function destroy() {
       
        return "Hello From Delete";
    }
}

Once you save everything, you can go to your browser or Postman and check if any of the routes work. For example, http://localhost:8000/api/v1/players should say ‘Hello from Index’. Please make sure your application is still running as a server otherwise your endpoints will not work.

Lumen API testing route
Testing Lumen route in browser

Create Lumen Models

The models are used to interact with the database. All your models have to do is specify the columns that are editable and the relationships between different tables. You should have one model per table. In our case, we will have a model called Player.php. This will be used by the controller class to interact with the data from the database. Please note that the names of models should be singular, so Player.php instead of Players.php.

In order for your models to work in Lumen, you must enable Eloquent. Do this by opening /bootstrap/app.php and uncommenting this line:

$app->withEloquent();

Save the file and follow the next steps:

  1. Create a folder called Models in /app.
  2. Create a file called Player.php in /app/Models.
  3. Copy and paste the code below in your Player.php file.
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Player extends Model
{

    /**
     * The attributes that are mass assignable
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'age',
        'nationality',
        'club',
        'gender',
    ];
}

Within the $fillable array you must specify all the columns you want to be editable from your database table. For example, if ‘name would not be specified in the array, you could not add/edit the value for the name.

NOTE: if you do not want to have the timestamps columns (created_at; updated_at) in your table, use the code below. Paste it as the first thing in your model class, before $fillable.

    /**
     * Have no timestamps columns in the table
     *
     * @var bool
     */
    public $timestamps = false;

Create CRUD functionality in Controllers

All the code in this section will go in the PlayersController, in /app/Http/Controllers.

Firstly, before our functions can use the Player model, we need to construct it at the top of our file. Create the $player variable and construct it. Make sure you import the Player model as well.

    /** @var Player */
    protected $player;
    
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct(Player $player)
    {
        $this->player = $player;
    }
// Use this line to import the Player model.
// Insert it after the namespace, at the top of the file
use App\Models\Player;

index() function – show all data

In your index() function you can now use the player model to perform a query to the database through Eloquent. Simply get all the data with one line of code and then return it. See the code below:

    /**
     * Return all the players
     */
    public function index() {
       
        $players = $this->player->all();

        return $players;
    }

Save the file and call the /players endpoint again in your browser or Postman. You should now see your data from the database being returned in a JSON format.

show() function – show specific data

Your show function will show a specific player, based on the ID passed into the route. Remember our route for this function is /players/{id}. This function will read the ID passed into the URL and check if there is a player corresponding to that ID in our database. See the code below:

    /**
     * Return a specific player
     */
    public function show($playerId) {
        
        $player = $this->player->find($playerId);

        if (empty($player)) {
               return "No data found.";
        }
       
        return $player;
    }

Save the file and call the /players/1 endpoint in your browser or Postman. You should now see your data from the database being returned in a JSON format. If you replace /1 with /test you the API should respond with “No data found.”. see the images below:

Lumen API see JSON data in browser
Test show() function in browser – success
Lumen API test error message in browser
Test show() function in browser – No data found

store() function – store data

This function deals with the creation of new data in your application. It must do at least two important things, validate the data from the request and store the data in the database. See the code below:

    /**
     * Create a new player
     */
    public function store(Request $request) {
        
        // Validate if the input for each field is correct 
        $this->validate($request, [
            'name' => 'required|string',
            'age' => 'required|integer',
            'nationality' => 'required|string',
            'club' => 'required|string',
            'gender' => 'required|string',
           ]);

        // Create the player
        $player = $this->player->create([
            'name' => $request->input('name'),
            'age' =>  $request->input('age'),
            'nationality' =>  $request->input('nationality'),
            'club' =>  $request->input('club'),
            'gender' =>  $request->input('gender'),
        ]);

        return $player;
    }

As you have probably noticed, we do not manually populate the created_at and updated_at columns in the database, Lumen takes care of that automatically. In order to use the Request class, you must import it at the top of the file, using the line below:

use Illuminate\Http\Request;

To test if your function works correctly, you must do a POST request to the /players endpoint, you can do this easily using a tool like Postman.

Lumen API test store method in Postman
Test store() function in Postman – success
Lumen API test store function in Postman - Fail validation
Test store() function in Postman – fail

In the picture above, you can see what happens when the validation requirements are not met. In this case, I did not specify a name (which is required) and the age is a string instead of an integer. Thus, the validation kicked in and returned the errors. You could also specify custom validation messages, in case you would like a more in-depth tutorial into Lumen Validation please let me know in the comments.

update() function – update data

This function takes in the ID of the player you want to update and then it updates the data based on what you specify in the request. So based off that, this function will take in the $request and $playerId, validate the data from the request as it did in the store() function and update the player information. See the code below:

    /**
     * Update info for a specific player
     */
    public function update(Request $request, $playerId) {
       
        // Validate if the input for each field is correct 
        $this->validate($request, [
            'name' => 'required|string',
            'age' => 'required|integer',
            'nationality' => 'required|string',
            'club' => 'required|string',
            'gender' => 'required|string',
           ]);

        // Find the player you want to update
        $player = $this->player->find($playerId);

        // Return error if not found
        if (empty($player)) {
            return "No data found.";
        }

        // Update the player
        $player->update([
            'name' => $request->input('name'),
            'age' =>  $request->input('age'),
            'nationality' =>  $request->input('nationality'),
            'club' =>  $request->input('club'),
            'gender' =>  $request->input('gender'),
        ]);

        return $player;
    }

Test this functionality by making a PATCH request to /players/3 through Postman.

Lumen API test update function in Postman - success
Test update() function in Postman – success
Lumen API test update function in postman - fail id not found
Test update() function in Postman – fail because player id no found

destroy() function – destroy data

This function will only take in the $playerId, check if it exists and delete it. In more advanced APIs and database structures, you will likely have data attached to this player. In that case, you will have to start detaching everything first and then delete the data, all done inside a database transaction. If you are interested to learn more about this let me know in the comments. See the code below for our function:

    /**
     * Delete a specific player
     */
    public function destroy($playerId) {
       
        $player = $this->player->find($playerId);

        if (empty($player)) {
            return "No data found.";
        }

        $player->delete();

        return;
    }

Test this functionality by making a DELTE request to players/1 through Postman. Player one should be deleted from the database and postman should return nothing with an HTTP code of 200.

Lumen API test destroy function in Postman - success
Test delete() function in Postman – success
Lumen API test destroy function in Postman - fail id not found
Test delete() function in Postman – fail player id not found

Your player one should now be deleted, if you do a GET call to /players through Postman or in your browser, you should not see an object with the id of one anymore.

Create Unit Tests – Test API

Before any piece of software goes into production and starts being used by the users, it should be tested. There are multiple kinds of functional tests you can do, like Unit testing. Integration testing. System testing. Sanity testing. Smoke testing. Interface testing. Regression testing.

Every time you realise a new feature or fix a bug you should test your entire application, just to make sure that everything still works as expected and it wasn’t affected by the new changes introduced.

In this section, we will focus on Unit Testing. This means testing of an individual software component or module. It’s generally performed by the programmer and not by testers because it requires a very good understanding of the internal program code and design.

In Lumen projects, the tests are located within the /tests directory. By default, this directory contains two files, one called TestCase.php which is extended and used by all test files, and one called ExampleTest.php which is the actual file that contains a basic test example. Each controller should have its own test file, in our case, for example, we will have a file called PlayerTests.php, this file will contain all the tests for each function. Optionally, you could also create another folder within /tests called PlayersController. Then you can add different files with individual sets of tests for each function within the controller, resulting in /tests/PlayersController/IndexTest.php and so on.

NOTE: All test files must follow this naming convention: SomeNameTest.php, otherwise the tests will not run.

I will be using a PHP extension called XDebug. It is really easy to install and use. Optionally you could use Zend Debugger for your tests. Please make sure PHP Xdebug is installed correctly on your system.

Better PHPUnit Extension VSCode

Since we are using VSCode, we will need to install an additional extension that will help us run the tests. I use Better PHPUnit in this tutorial but there are other good extensions available. Once the extension is installed we can move to our test file.

Rename ExampleTest to PlayerTest. Then use the code below to test the index function.

    public function testIndex()
    {
        $this->get('api/v1/players')
        ->seeStatusCode(200)
        ->seeJsonStructure([
            '*' => [
                'id',
                'name',
                'age',
                'nationality',
                'club',
                'gender',
                'created_at',
                'updated_at',
            ]
        ]);
    }

As you can see, this simple test calls the players endpoint and expects to get an HTTP status code of 200 and checks if the JSON structure returned by the API matches what I have specified. Check out all the tests in GitHub. You can run all the tests by doing CTRL + SHIFT + P and selecting Better PHPUnit: run suite. Make sure the PlayerTest.php file is opened and selected. All tests and assertions should pass successfully.

Please note that at the moment the tests are using your local SQLite database without migrating and reseeding every time a test has run. This will affect your local data. If you would like to know how to run the tests in memory please let me know in the comments.

Lumen API Run PHPUnit Tests using Xdebug
Run tests using Better PHPUnit – all pass successfully

Conclusion

Congratulations if you have got so far. You should now have a basic PHP API running locally. There are many things that could be improved, like throwing proper HTTP errors in JSON format in case of failure and using resources to return the data in a standardized way. Remember that you can access and use all the source code for this project from GitHub. For more advanced topics or help please get in touch.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.