Lumen Api Setup
ProgrammingLumen is a micro-framework written in PHP by the designers of Laravel. I needed to learn how to use an API that is written PHP. The reason for this is that unlike Python or Java frameworks, PHP can be dropped on almost any web hosting site there is, whereas other languages require more specialised hosting.
API Setup⌗
Setting up Lumen is a fairly well documented process but I will go over some of the parts that tripped me up initially.
Project Creation⌗
Using composer
compoer create-project --prefer-dist laravel/lumen <project folder>
Environment Variables⌗
First up editing the included .env
file. We will be setting up the database later so you
can delete the entries for anything database related.
APP_NAME=Project
APP_ENV=local
APP_KEY=MakeSureYouFillThisInWithARandomValue
APP_DEBUG=true
APP_URL=http://localhost
APP_TIMEZONE=UTC
LOG_CHANNEL=stack
LOG_SLACK_WEBHOOK_URL=
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
Database Configuration⌗
Create a new file under the config directory and call it database.php
. This file will tell
lumen how to connect to ou chosen database.
<?php
return [
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => LEAGUE_CODE . '_',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'sqlite' => [
'driver' => 'sqlite',
'database' => env('DB_DATABASE', storage_path('database.sqlite')),
'prefix' => env('DB_PREFIX', ''),
],
],
];
Bootstrapping the App⌗
To ensure the application will run correctly there are a few minor edits we need to change
in the bootstrap/app.php
file. This file is responsible for setting up the application
on each request.
First up we need to enable facades and eloquent. SO uncomment the following lines:
$app->withFacades();
$app->withEloquent();
Whilst we are here, we can also add teh configuration required for the swagger-lume library that we will configure next. To do this we need to add a new line underneath the app config line
$app->configure('app');
// Add this line just here
$app->configure('swagger-lume');
Swagger Documentation⌗
For documenting APIs that I create I prefer to use Swagger. By using swagger we can also get a live, intewractive, hosted version of our docs using swagger-ui. The library for this is called swagger-lume, which can be installed by using composer
composer require "darkaonline/swagger-lume:7.*"
Once we have the library downloaded we need to create the configuration file. For simplicity, you can download my config file:
Some important sections to note here are as follows:
- All the URL routes are behind a common endpoint
/api
. - No middleware is included for protecting the documentation
The resasons for this are to do with the reverse proxy that is explained later in the post.
Adding a Test Route⌗
Here we will add a a dummy route that we can query to ensure that our site is setup
correctly. We need to add this because once behind the proxy, the default route included
by lumen (that prints the app verion), should be inaccessible. This route is added to
the file routes/web.php
$router->group(['prefix' => 'api'], function() use ($router) {
$router->get('/', function() use ($router) {
return response()->json([
"hello" => "world",
], 200);
});
});
Docker and Nginx⌗
docker-compose is used here to connect and run all the services. There are 3 main services registered here: nginx, backend and db.
- Nginx is the reverse proxy that routes traffic
- Backend is our Lumen API
- DB is a MySQL database for our backend to use
Also defined are two networks, front and back. The front network is accessible to the world and the backend network is private. The main use here is to ensure that our database is not world accessible.
version: "3"
volumes:
db:
networks:
front:
back:
services:
nginx:
image: lumenapi/nginx:v1
build:
dockerfile: docker/nginx/Dockerfile
context: .
depends_on:
- backend
- frontend
networks:
- front
ports:
- "80:80"
volumes:
- ./backend:/var/www/html/api
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
backend:
image: lumenapi/backend:v1
build:
context: .
dockerfile: docker/backend/Dockerfile
depends_on:
- redis
- db
environment:
- DB_HOST=db
- DB_PORT=3306
- DB_DATABASE=lumenapi
- DB_USERNAME=lumenapi
- DB_PASSWORD=helloworld
expose:
- 9000
networks:
- front
- back
db:
image: linuxserver/mariadb
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- MYSQL_ROOT_PASSWORD=helloworld
- TZ=Europe/London
- MYSQL_DATABASE=lumenapi
- MYSQL_USER=lumenapi
- MYSQL_PASSWORD=helloworld
volumes:
- db:/config
networks:
- back
ports:
- 3306:3306
Directory Structure⌗
.
|- backend/
| |- app/
| |- artisan
| |- bootstrap/
| |- ...
|- docker/
| |- backend/
| | |- Dockerfile
| |- nginx/
| | |- Dockerfile
| | |- default.conf
|- docker-compose.yml
docker/backend/Dockerfile⌗
FROM composer as composer
WORKDIR /app
COPY backend/ /app
RUN composer install
FROM php:7.4-fpm-alpine
RUN docker-php-ext-install mysqli pdo pdo_mysql
COPY backend/ /var/www/html/api
COPY --from=composer /app/vendor /var/www/html/api/vendor
RUN chown -R www-data:www-data /var/www/html
WORKDIR /var/www/html
EXPOSE 9000
docker/nginx/Dockerfile⌗
FROM nginx:latest
WORKDIR /var/www/html
COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
docker/nginx/default.conf⌗
There is configuration here for a frontend as well. This could be, for example, some kind
of JavaScript frontend (like Vue or React). This config is included so that if you decide
to sping up a frontend int eh docker-compose, you can just uncomment these lines and the
app should work
Something that tripped me up for a while was the fact that the paths must be the same in both the nginx and backend container e.g. the code should be stored in /var/www/html in both containers
server {
listen 80;
server_name _;
index index.php index.html index.html;
charset utf-8;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
# # Requests to the frontend
# location / {
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_pass http://frontend:8080;
# }
# Requests to the Lumen API
location ^~ /api {
root /var/www/html;
index index.php;
try_files $uri /api/public/index.php;
location ~ \.php {
try_files $uri =404;
fastcgi_pass backend:9000;
fastcgi_index index.php;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
}
Running the API⌗
To run the API you should now be able to run:
docker-compose up
We can then test our API by heading over to the browser and navigating to
- http://localhost/
- Should return a 404 page
- http://localhost/api
- Should return our hello world route as defined in
backend/route/web.php
- Should return our hello world route as defined in