How to Build an API with the Lumen Micro-Framework

How-to-build-an-API-with-the-Lumen-Micro-frameworkMicro-frameworks like Lumen are slimmed down versions of normal web frameworks, containing only the most important functionalities. This doesn’t mean they lack functionalities, but that their core is simple yet extensible.

Micro-frameworks are great for developing APIs. Why? In a word: performance. Their minimalistic core makes them incredibly fast. Most of them, including Lumen, also include very simple ways for creating a RESTful architecture.

On the blog, we’ve explored the Spark and Play frameworks. Now, in this code tutorial, we develop an example API using the Lumen Micro-framework. Before starting, it’s important to note that one of the plus points of a Lumen project is that it’s also incredibly easy to transform it into a Laravel project, which is a full-fledged PHP framework. This makes it feasible to quickly prototype something small, and expand on it later on.

Installing Lumen

We can download the lumen-installer, which will help us easily start new Lumen projects:

composer global require "laravel/lumen-installer"

Once we have this globally installed, we can create a new Lumen project just by typing the following command:

lumen new PROJECT_NAME

This will create a folder PROJECT_NAME with all the dependencies already installed. That’s it, we’re ready to start.

Configuration

Lumen uses DotEnv for environment-based configuration. You can read the source-code and a short guide on it directly from its repository.

To start off let’s open the .env.example file:

APP_ENV=local
APP_DEBUG=true
APP_KEY=SomeRandomKey!!!

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

CACHE_DRIVER=memcached
QUEUE_DRIVER=sync

Let’s update the values with the ones on our local machine, and then rename the file to .env. Configuration is done!

Enabling features

Contrary to Laravel, which has different features in many different files, almost everything in Lumen is bootstrapped from bootstrap/app.php. That’s where we must head to for enabling/disabling different functions, based on our needs.

Somewhere in the file we’ll see this:

// $app->withFacades();

// $app->withEloquent();

That’s right, both Facades and Eloquent are disabled by default. Facades are a nice and easy way to access classes in the service container, while Eloquent is an ActiveRecord implementation to interface objects with your database. If you’re familiar with Laravel you know these are two of the biggest features of the framework. Here they’re disabled primarily for performance purposes, however enabling them in Lumen is as easy as uncommenting those two lines.

In the same app.php file we can register middlewares (both application-wide or route-specific), and service providers.

A simple API example: Base64 Encoder

Let’s create a simple API that encodes strings in base64, and decodes them too. We don’t need a database for this, so we’ll start by creating two routes in app/Http/routes.php:

$app->get('encode', function (\Illuminate\Http\Request $request) {
    return response()->json([
        'result' => base64_encode($request->input('value')),
    ]);
});

$app->get('decode', function (\Illuminate\Http\Request $request) {
    return response()->json([
        'result' => base64_decode($request->input('value')),
    ]);
});

To test it we can’t use artisan serve as it’s been removed starting from Lumen 5.2, so let’s go in the project root folder and let’s run PHP’s internal web-server, remembering to also set the document root to public/:

php -S localhost:8000 -t public/

Now we can try it in our browser with a normal GET request:

http://localhost:8000/encode?value=lumen%20rocks

We get:

{"result":"bHVtZW4gcm9ja3M="}

Let’s now feed the result to the decode function:

http://localhost:8000/decode?value=bHVtZW4gcm9ja3M

Which brings back our original string:

{"result":"lumen rocks"}
Download our free development guide

Adding some complexities: Gym Membership example

Let’s assume we’re creating a SaaS software that allows a gym to manage their memberships. The API must then be able to Create, Read, Update, and Delete gym membership profiles.

We’ll first have to create a migration with the database schema for the memberships. We can use an artisan command to prepare the migration file:

artisan make:migration create_memberships_table --create="memberships"

Let’s open the file, and add a few columns to the table. We should end up with something like this:

increments('id');
            $table->integer('user_id')->unsigned();
            $table->date('started_at');
            $table->date('last_renewed_at')->nullable();
            $table->date('active_until');
            $table->timestamps();

            $table->foreign('user_id')
                    ->references('id')->on('users')
                    ->onUpdate('cascade')
                    ->onDelete('restrict');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down()
    {
        Schema::drop('memberships');
    }
}

This is of course a simplified example, as a real SaaS should at a minimum have a gym_id field to distinguish memberships between several customer gyms.

Next up we want to create the Eloquent ORM model to interface the memberships with the database. Let’s create a file app/Membership.php with the following:

The $fillable property allows us to set a limit to the kind of properties that can be assigned with a single call. It’s mostly irrelevant in this case, but in some cases (like creating new users) it helps to avoid the mass-assignment vulnerability.

The $table property lets Eloquent know how to interact with the database through this specific class.

Since Membership extends Eloquent/Model directly, it’s already set up for all the basic CRUD operations that we’ll need for our API.

Now we can move to actually create the routes for making the API requests. Instead of anonymous functions like the previous example, we’ll route the requests to a controller function:

group(['prefix' => 'api/v1', 'namespace' => 'App/Http/Controllers'], function ($app) { $app->group(['prefix' => 'membership'], function ($app) { // Returns all the memberships // GET http://localhost:8000/membership $app->get('/', 'MembershipController@index'); // Returns the membership with the chosen $id // e.g. GET http://localhost:8000/membership/48 $app->get('{id}', 'MembershipController@read'); // Creates a new membership // POST http://localhost:8000/membership $app->post('/', 'MembershipController@create'); // Update the membership with the chosen $id // PUT http://localhost:8000/membership/48 $app->put('{id}', 'MembershipController@update'); // Delete the membership with the chosen $id // DELETE http://localhost:8000/membership/48 $app->delete('{id}', 'MembershipController@delete'); }); });

And finally the controller in which we handle the different requests:

json($memberships); } public function read($id) { $membership = Membership::find($id); return response()->json($membership); } public function create(Request $request) { $membership = Membership::create($request->all()); return response()->json($membership); } public function update(Request $request, $id) { $membership = Membership::find($id); $updated = $membership->update($request->all()); return response()->json(['updated' => $updated]); } public function delete($id) { $deletedRows = Membership::destroy($id); $deleted = $deletedRows == 1; return response()->json(['deleted' => $deleted]); } }

As you may notice the $id is automatically passed by Lumen from the routes to the function as a parameter. Additionally, in the create and update methods, an \Illuminate\Http\Request variable is type-hinted as one of the parameters. This allows Lumen to directly provide it from its Service Container. This is usually referred to as method-level Dependency Injection.

That’s all it takes with Lumen to create a simple API. You can use POSTMAN to make HTTP requests other than GET and see the code working. POSTMAN is an extension for Chrome that allows you to make requests by choosing the HTTP method too, which is perfect to test APIs.

Conclusion

lumen logoWe’ve seen firsthand the power of micro-frameworks and why they’re suited to handle API architectures. Lumen is an incredibly versatile framework, and we’ve gone through how to get started with it to quickly create an API. Of course our examples are not production ready — a real API would need to consider authentication, authorization, validation, throttling, and more. Many of which Lumen has already built-in, and we can simply leverage. You should always go through the documentation before coding something yourself, otherwise you risk re-inventing the wheel.

More Resources

Giovanni Casinelli

About Giovanni Casinelli

Co-founder & CTO at BonAppetour. Formerly CEO at Asteroid. Passionate about new technologies and how they shape the future. Building and improving APIs at BonAppetour to allow everyone to eat with locals when traveling.