O Lumen é o micro-framework do Laravel otimizado para criar APIs e microservices extremamente rápidos. Ao remover as funcionalidades full-stack do Laravel, o Lumen oferece um desempenho impressionante, mantendo a sintaxe elegante do Laravel. Este guia aborda a criação de APIs de produção com Lumen na perspetiva de um developer sénior.
Porquê Lumen
O Lumen destaca-se em cenários específicos:
- Desempenho: Mais rápido do que o Laravel completo para aplicações apenas de API
- Sintaxe Familiar: Os mesmos padrões do Laravel
- Microservices: Perfeito para serviços pequenos e focados
- Baixa Sobrecarga: Apenas o que precisa, nada mais
- Atualização Fácil: Pode migrar para o Laravel quando necessário
Ideal para: APIs, microservices, handlers de webhooks e serviços que não precisam de views, sessões ou cookies.
Instalação
# Criar novo projeto Lumen
composer create-project --prefer-dist laravel/lumen my-api
# Navegar para o projeto
cd my-api
# Iniciar servidor de desenvolvimento
php -S localhost:8000 -t public
Estrutura do Projeto
my-api/
├── app/
│ ├── Console/
│ ├── Events/
│ ├── Exceptions/
│ ├── Http/
│ │ ├── Controllers/
│ │ └── Middleware/
│ ├── Jobs/
│ ├── Listeners/
│ ├── Models/
│ └── Providers/
├── bootstrap/
│ └── app.php
├── config/
├── database/
│ ├── factories/
│ ├── migrations/
│ └── seeders/
├── public/
│ └── index.php
├── resources/
├── routes/
│ └── web.php
├── storage/
├── tests/
├── .env
└── composer.json
Configuração
Ativar Funcionalidades
Em bootstrap/app.php, ative o que precisar:
<?php
require_once __DIR__.'/../vendor/autoload.php';
(new Laravel\Lumen\Bootstrap\LoadEnvironmentVariables(
dirname(__DIR__)
))->bootstrap();
$app = new Laravel\Lumen\Application(
dirname(__DIR__)
);
// Ativar facades (opcional, para sintaxe ao estilo do Laravel)
$app->withFacades();
// Ativar o Eloquent ORM
$app->withEloquent();
// Registar service providers
$app->register(App\Providers\AppServiceProvider::class);
$app->register(App\Providers\AuthServiceProvider::class);
// $app->register(App\Providers\EventServiceProvider::class);
// Carregar middleware
$app->middleware([
App\Http\Middleware\CorsMiddleware::class,
]);
$app->routeMiddleware([
'auth' => App\Http\Middleware\Authenticate::class,
'throttle' => App\Http\Middleware\ThrottleRequests::class,
]);
// Carregar rotas
$app->router->group([
'namespace' => 'App\Http\Controllers',
], function ($router) {
require __DIR__.'/../routes/web.php';
});
return $app;
Variáveis de Ambiente
.env:
APP_NAME=MyAPI
APP_ENV=local
APP_KEY=base64:your-random-32-character-key
APP_DEBUG=true
APP_URL=http://localhost
APP_TIMEZONE=UTC
LOG_CHANNEL=stack
LOG_SLACK_WEBHOOK_URL=
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapi
DB_USERNAME=root
DB_PASSWORD=
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
Routing
Rotas Básicas
routes/web.php:
<?php
/** @var \Laravel\Lumen\Routing\Router $router*/
// Verificação de saúde
$router->get('/', function () {
return response()->json([
'name' => config('app.name'),
'version' => '1.0.0',
'status' => 'ok'
]);
});
// Rotas de recursos
$router->get('/users', 'UserController@index');
$router->get('/users/{id}', 'UserController@show');
$router->post('/users', 'UserController@store');
$router->put('/users/{id}', 'UserController@update');
$router->delete('/users/{id}', 'UserController@destroy');
// Grupos de rotas
$router->group(['prefix' => 'api/v1'], function () use ($router) {
// Rotas públicas
$router->post('/auth/login', 'AuthController@login');
$router->post('/auth/register', 'AuthController@register');
// Rotas protegidas
$router->group(['middleware' => 'auth'], function () use ($router) {
$router->get('/me', 'AuthController@me');
$router->post('/auth/logout', 'AuthController@logout');
// Utilizadores
$router->get('/users', 'UserController@index');
$router->get('/users/{id}', 'UserController@show');
$router->put('/users/{id}', 'UserController@update');
$router->delete('/users/{id}', 'UserController@delete');
});
});
Rota com Middleware
$router->group([
'prefix' => 'admin',
'middleware' => ['auth', 'admin'],
], function () use ($router) {
$router->get('/dashboard', 'AdminController@dashboard');
$router->get('/users', 'AdminController@users');
});
Controllers
Controller Básico
app/Http/Controllers/UserController.php:
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class UserController extends Controller
{
/**
* Listar todos os utilizadores com paginação.
*/
public function index(Request $request): JsonResponse
{
$this->validate($request, [
'page' => 'integer|min:1',
'limit' => 'integer|min:1|max:100',
'search' => 'string|max:100',
]);
$limit = $request->input('limit', 20);
$query = User::query();
if ($search = $request->input('search')) {
$query->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
}
$users = $query->paginate($limit);
return response()->json([
'data' => $users->items(),
'meta' => [
'total' => $users->total(),
'page' => $users->currentPage(),
'limit' => $users->perPage(),
'pages' => $users->lastPage(),
]
]);
}
/**
* Obter um único utilizador.
*/
public function show(int $id): JsonResponse
{
$user = User::find($id);
if (!$user) {
return response()->json([
'error' => 'User not found'
], 404);
}
return response()->json(['data' => $user]);
}
/**
* Criar novo utilizador.
*/
public function store(Request $request): JsonResponse
{
$this->validate($request, [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:8',
]);
$user = User::create([
'name' => $request->input('name'),
'email' => $request->input('email'),
'password' => app('hash')->make($request->input('password')),
]);
return response()->json([
'data' => $user,
'message' => 'User created successfully'
], 201);
}
/**
* Atualizar utilizador.
*/
public function update(Request $request, int $id): JsonResponse
{
$user = User::find($id);
if (!$user) {
return response()->json([
'error' => 'User not found'
], 404);
}
$this->validate($request, [
'name' => 'string|max:255',
'email' => 'email|unique:users,email,' . $id,
]);
$user->update($request->only(['name', 'email']));
return response()->json([
'data' => $user,
'message' => 'User updated successfully'
]);
}
/**
* Eliminar utilizador.
*/
public function destroy(int $id): JsonResponse
{
$user = User::find($id);
if (!$user) {
return response()->json([
'error' => 'User not found'
], 404);
}
$user->delete();
return response()->json(null, 204);
}
}
Middleware
Middleware de CORS
app/Http/Middleware/CorsMiddleware.php:
<?php
namespace App\Http\Middleware;
use Closure;
class CorsMiddleware
{
public function handle($request, Closure $next)
{
$headers = [
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-Requested-With',
'Access-Control-Max-Age' => '86400',
];
if ($request->isMethod('OPTIONS')) {
return response()->json(null, 200, $headers);
}
$response = $next($request);
foreach ($headers as $key => $value) {
$response->header($key, $value);
}
return $response;
}
}
Middleware de Autenticação
app/Http/Middleware/Authenticate.php:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Factory as Auth;
class Authenticate
{
protected $auth;
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
public function handle($request, Closure $next, $guard = null)
{
if ($this->auth->guard($guard)->guest()) {
return response()->json([
'error' => 'Unauthorized'
], 401);
}
return $next($request);
}
}
Operações de Base de Dados
Queries em Bruto
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\DB;
class ProductController extends Controller
{
public function index(Request $request)
{
$this->validate($request, [
'price_min' => 'integer|min:0',
'price_max' => 'integer|min:0',
'page' => 'integer|min:0',
]);
$priceMin = $request->input('price_min', 0);
$priceMax = $request->input('price_max', 0);
$page = $request->input('page', 0);
$pageLength = 30;
$products = DB::select("
SELECT p.id, p.name, p.price, c.name as category
FROM products p
LEFT JOIN categories c ON p.category_id = c.id
WHERE TRUE
AND (:price_min = 0 OR p.price >= :price_min1)
AND (:price_max = 0 OR p.price <= :price_max1)
ORDER BY p.created_at DESC
LIMIT :page_length OFFSET :offset
", [
'price_min' => $priceMin,
'price_min1' => $priceMin,
'price_max' => $priceMax,
'price_max1' => $priceMax,
'page_length' => $pageLength,
'offset' => $page * $pageLength,
]);
if (empty($products)) {
return response()->json([
'message' => 'No products found'
], 404);
}
return response()->json([
'data' => $products
]);
}
}
Modelos Eloquent
app/Models/User.php:
<?php
namespace App\Models;
use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Lumen\Auth\Authorizable;
class User extends Model implements AuthenticatableContract, AuthorizableContract
{
use Authenticatable, Authorizable, HasFactory;
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
public function posts()
{
return $this->hasMany(Post::class);
}
}
Validação
No Controller
public function store(Request $request)
{
$this->validate($request, [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
'role' => 'in:user,admin',
'tags' => 'array',
'tags.*' => 'string|max:50',
]);
// Validação aprovada, continuar...
}
Resposta de Erro Personalizada
app/Exceptions/Handler.php:
<?php
namespace App\Exceptions;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Validation\ValidationException;
use Laravel\Lumen\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;
class Handler extends ExceptionHandler
{
public function render($request, Throwable $exception)
{
if ($exception instanceof ValidationException) {
return response()->json([
'error' => 'Validation failed',
'details' => $exception->errors()
], 422);
}
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'error' => 'Resource not found'
], 404);
}
if ($exception instanceof AuthorizationException) {
return response()->json([
'error' => 'Forbidden'
], 403);
}
if ($exception instanceof HttpException) {
return response()->json([
'error' => $exception->getMessage()
], $exception->getStatusCode());
}
// Registar erros inesperados
if (env('APP_DEBUG')) {
return response()->json([
'error' => $exception->getMessage(),
'trace' => $exception->getTrace()
], 500);
}
return response()->json([
'error' => 'Internal server error'
], 500);
}
}
Resposta JSON em UTF-8
Garanta a codificação correta:
return response()->json($data, 200, [], JSON_UNESCAPED_UNICODE);
Ou globalmente num middleware:
public function handle($request, Closure $next)
{
$response = $next($request);
if ($response->headers->get('Content-Type') === 'application/json') {
$response->setEncodingOptions(JSON_UNESCAPED_UNICODE);
}
return $response;
}
Documentação da API
Use mpociot/laravel-apidoc-generator:
composer require mpociot/laravel-apidoc-generator
# Gerar documentação
php artisan apidoc:generate
Principais Conclusões
- Ative apenas o que precisa: Facades e Eloquent são opcionais
- Use grupos de rotas: Organize e aplique middleware de forma eficiente
- Valide tudo: Nunca confie no input
- Trate os erros de forma elegante: Handler de exceções personalizado
- Mantenha os controllers simples: Mova a lógica para services
- Considere fazer upgrade: Migre para o Laravel se precisar de mais funcionalidades
O Lumen oferece um desempenho excecional para workloads de API — perfeito para microservices onde cada milissegundo conta.