Rest API boilerplate for Lumen micro-framework.
A RESTful API boilerplate for Lumen micro-framework. Features included:
First, clone the repo:
bash $ git clone [email protected]:hasib32/rest-api-with-lumen.git
You can use Laravel Homestead globally or per project for local development. Follow the Installation Guide.
$ cd rest-api-with-lumen $ composer install
Create
.envfile:
$ cat .env.example > .envIf you want you can edit database name, database username and database password.
First, we need connect to the database. For homestead user, login using default homestead username and password:
bash $ mysql -uhomestead -psecret
Then create a database:
bash mysql> CREATE DATABASE restapi;
And also create test database:
bash mysql> CREATE DATABASE restapi_test;
Run the Artisan migrate command with seed:
bash $ php artisan migrate --seed
Create "personal access" and "password grant" clients which will be used to generate access tokens:
bash $ php artisan passport:install
You can find those clients in
oauth_clientstable.
| HTTP Method | Path | Action | Scope | Desciption | | ----- | ----- | ----- | ---- |------------- | | GET | /users | index | users:list | Get all users | POST | /users | store | users:create | Create an user | GET | /users/{userid} | show | users:read | Fetch an user by id | PUT | /users/{userid} | update | users:write | Update an user by id | DELETE | /users/{user_id} | destroy | users:delete | Delete an user by id
Note:
users/meis a special route for getting current authenticated user. And for all User routes 'users' scope is available if you want to perform all actions.
Visit dusterio/lumen-passport to see all the available
OAuth2routes.
Since Laravel Passport doesn't restrict any user creating any valid scope. I had to create a route and controller to restrict user creating access token only with permitted scopes. For creating accesstoken we have to use the
accessTokenroute. Here is an example of creating accesstoken for grant_type password with Postman.
http://stackoverflow.com/questions/39436509/laravel-passport-scopes
Creating a new resource is very easy and straight-forward. Follow these simple steps to create a new resource.
Create a new route name
messages. Open the
routes/web.phpfile and add the following code:
$route->post('messages', [ 'uses' => '[email protected]', 'middleware' => "scope:messages,messages:create" ]); $route->get('messages', [ 'uses' => '[email protected]', 'middleware' => "scope:messages,messages:list" ]); $route->get('messages/{id}', [ 'uses' => '[email protected]', 'middleware' => "scope:messages,messages:read" ]); $route->put('messages/{id}', [ 'uses' => '[email protected]', 'middleware' => "scope:messages,messages:write" ]); $route->delete('messages/{id}', [ 'uses' => '[email protected]', 'middleware' => "scope:messages,messages:delete" ]);
For more info please visit Lumen Routing page.
Create
MessageModel inside
App/Modelsdirectory and create migration using Lumen Artisan command.
Message Model
use Illuminate\Database\Eloquent\Model;class Message extends Model { /** * The database table used by the model. * * @var string */ protected $table = 'messages';
/** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'uid', 'userId', 'subject', 'message', ];
}
Visit Laravel Eloquent Page for more info about Model.
Create migration for messages table
php artisan make:migration create_messages_table --create=messages
Migration file
class CreateMessagesTable extends Migration { public function up() { Schema::create('messages', function (Blueprint $table) { $table->increments('id'); $table->string('uid', 36)->unique(); $table->integer('userId')->unsigned(); $table->string('subject')->nullable(); $table->longText('message'); $table->timestamps();$table->foreign('userId') ->references('id')->on('users') ->onDelete('cascade') ->onUpdate('cascade'); }); }
}
For more info visit Laravel Migration page.
Create
MessageRepositoryand implementation of the repository name
EloquentMessageRepository.
MessageRepository
interface MessageRepository extends BaseRepository { }
EloquentMessageRepository
use App\Models\Message; use App\Repositories\Contracts\MessageRepository;class EloquentMessageRepository extends AbstractEloquentRepository implements MessageRepository { /** * Model name. * * @var string */ protected $modelName = Message::class; }
Next, update
RepositoriesServiceProviderto bind the implementation:
use Illuminate\Support\ServiceProvider; use App\Repositories\Contracts\UserRepository; use App\Repositories\EloquentUserRepository; use App\Repositories\Contracts\MessageRepository; use App\Repositories\EloquentMessageRepository;class RepositoriesServiceProvider extends ServiceProvider { /** * Indicates if loading of the provider is deferred. * * @var bool */ protected $defer = true;
/** * Register any application services. * * @return void */ public function register() { $this->app->bind(UserRepository::class, function () { return new EloquentUserRepository(new User()); }); $this->app->bind(MessageRepository::class, function () { return new EloquentMessageRepository(new Message()); }); } /** * Get the services provided by the provider. * * @return array */ public function provides() { return [ UserRepository::class, MessageRepository::class, ]; }
}
Visit Lumen documentation for more info about Service Provider.
Fractal provides a presentation and transformation layer for complex data output, the like found in RESTful APIs, and works really well with JSON. Think of this as a view layer for your JSON/YAML/etc.
Create a new Transformer name
MessageTransformerinside
app/Transformersdirectory:
use App\Models\Message; use League\Fractal\TransformerAbstract;class MessageTransformer extends TransformerAbstract { public function transform(Message $message) { return [ 'id' => $message->uid, 'userId' => $message->userId, 'subject' => $message->subject, 'message' => $message->message, 'createdAt' => (string) $message->created_at, 'updatedAt' => (string) $message->updated_at, ]; } }
Visit Fractal official page for more information.
For authorization we need to create policy that way basic user can't show or edit other user messages.
MessagePolicy
use App\Models\User; use App\Models\Message;class MessagePolicy { /** * Intercept checks. * * @param User $currentUser * @return bool */ public function before(User $currentUser) { if ($currentUser->isAdmin() && (!$currentUser->tokenCan('basic') || $currentUser->tokenCan('undefined'))) { return true; } }
/** * Determine if a given user has permission to show. * * @param User $currentUser * @param Message $message * @return bool */ public function show(User $currentUser, Message $message) { return $currentUser->id === $message->userId; } /** * Determine if a given user can update. * * @param User $currentUser * @param Message $message * @return bool */ public function update(User $currentUser, Message $message) { return $currentUser->id === $message->userId; } /** * Determine if a given user can delete. * * @param User $currentUser * @param Message $message * @return bool */ public function destroy(User $currentUser, Message $message) { return $currentUser->id === $message->userId; }
}
Next, update
AuthServiceProviderto use the policy:
Gate::policy(Message::class, MessagePolicy::class);And add scopes to
Passport::tokensCan:
[ 'messages' => 'Messages scope', 'messages:list' => 'Messages scope', 'messages:read' => 'Messages scope for reading records', 'messages:write' => 'Messages scope for writing records', 'messages:create' => 'Messages scope for creating records', 'messages:delete' => 'Messages scope for deleting records' ]Visit Lumen Authorization Page for more info about Policy.
Finally, let's create the
MessageController. Here we're using MessageRepository, MessageTransformer and MessagePolicy.
use App\Models\Message; use App\Repositories\Contracts\MessageRepository; use Illuminate\Http\Request; use App\Transformers\MessageTransformer;class MessageController extends Controller { /** * Instance of MessageRepository. * * @var MessageRepository */ private $messageRepository;
/** * Instanceof MessageTransformer. * * @var MessageTransformer */ private $messageTransformer; /** * Constructor. * * @param MessageRepository $messageRepository * @param MessageTransformer $messageTransformer */ public function __construct(MessageRepository $messageRepository, MessageTransformer $messageTransformer) { $this->messageRepository = $messageRepository; $this->messageTransformer = $messageTransformer; parent::__construct(); } /** * Display a listing of the resource. * * @param Request $request * @return \Illuminate\Http\JsonResponse */ public function index(Request $request) { $messages = $this->messageRepository->findBy($request->all()); return $this->respondWithCollection($messages, $this->messageTransformer); } /** * Display the specified resource. * * @param $id * @return \Illuminate\Http\JsonResponse|string */ public function show($id) { $message = $this->messageRepository->findOne($id); if (!$message instanceof Message) { return $this->sendNotFoundResponse("The message with id {$id} doesn't exist"); } // Authorization $this->authorize('show', $message); return $this->respondWithItem($message, $this->messageTransformer); } /** * Store a newly created resource in storage. * * @param Request $request * @return \Illuminate\Http\JsonResponse|string */ public function store(Request $request) { // Validation $validatorResponse = $this->validateRequest($request, $this->storeRequestValidationRules($request)); // Send failed response if validation fails if ($validatorResponse !== true) { return $this->sendInvalidFieldResponse($validatorResponse); } $message = $this->messageRepository->save($request->all()); if (!$message instanceof Message) { return $this->sendCustomResponse(500, 'Error occurred on creating Message'); } return $this->setStatusCode(201)->respondWithItem($message, $this->messageTransformer); } /** * Update the specified resource in storage. * * @param Request $request * @param $id * @return \Illuminate\Http\JsonResponse */ public function update(Request $request, $id) { // Validation $validatorResponse = $this->validateRequest($request, $this->updateRequestValidationRules($request)); // Send failed response if validation fails if ($validatorResponse !== true) { return $this->sendInvalidFieldResponse($validatorResponse); } $message = $this->messageRepository->findOne($id); if (!$message instanceof Message) { return $this->sendNotFoundResponse("The message with id {$id} doesn't exist"); } // Authorization $this->authorize('update', $message); $message = $this->messageRepository->update($message, $request->all()); return $this->respondWithItem($message, $this->messageTransformer); } /** * Remove the specified resource from storage. * * @param $id * @return \Illuminate\Http\JsonResponse|string */ public function destroy($id) { $message = $this->messageRepository->findOne($id); if (!$message instanceof Message) { return $this->sendNotFoundResponse("The message with id {$id} doesn't exist"); } // Authorization $this->authorize('destroy', $message); $this->messageRepository->delete($message); return response()->json(null, 204); } /** * Store Request Validation Rules * * @param Request $request * @return array */ private function storeRequestValidationRules(Request $request) { return [ 'userId' => 'required|exists:users,id', 'subject' => 'required', 'message' => 'required', ]; } /** * Update Request validation Rules * * @param Request $request * @return array */ private function updateRequestValidationRules(Request $request) { return [ 'subject' => '', 'message' => '', ]; }
}
Visit Lumen Controller page for more info about Controller.
To see the step-by-step tutorial how I created this boilerplate please visit our blog devnootes.net.
Contributions, questions and comments are all welcome and encouraged. For code contributions submit a pull request.
Taylor Otwell, Shahriar Mahmood, Fractal, Phil Sturgeon