@industry/apikit
v1.2.0
Published
REST endpoints for any Laravel model made simple.
Downloads
5
Readme
ApiKit
REST endpoints for any Laravel Model made simple.
Table of contents
Installation
Backend: composer require weka/apikit
Frontend: yarn add @industry/apikit
Usage in your backend
Adding a model
Resolve Weka\ApiKit\ResourceRegistry
via Laravel's DI-Container and add your model with a compatible policy:
MyModel.php
class MyModel extends \Illuminate\Database\Eloquent\Model
{
//
}
MyPolicy.php
use Illuminate\Contracts\Auth\Access\Authorizable;
class MyPolicy
{
public function create(Authorizable $user = null)
{
return $user !== null;
}
//...
}
Any Service Provider, eg. AppServiceProvider.php
$registry = resolve(Weka\ApiKit\ResourceRegistry::class);
$resourceHandler = $registry->add(MyModel::class, MyPolicy::class);
// Use the following statement to get all your model's routes:
// $resourceHandler->getRoutes();
Adding a resource instead of a model
If you need more control over the behaviour add a resource instead of a model:
MyModel.php
class MyModel extends \Illuminate\Database\Eloquent\Model
{
//
}
MyPolicy.php
use Illuminate\Contracts\Auth\Access\Authorizable;
class MyPolicy
{
public function create(Authorizable $user = null)
{
return $user !== null;
}
//...
}
MyResource.php
use Illuminate\Http\Request;
class MyResource extends \Weka\ApiKit\ResourceRegistry\AbstractResource
{
/**
* Returns the fully qualified class name of the model
*
* @return string
*/
public function getModelClassName() : string
{
// This is the model you want to access.
return MyModel::class;
}
/**
* Returns the resource's routeParameterName. This is also used as the route's parameter name.
*
* @return string
*/
public function public function getRouteParameterName() : string
{
// This is the route parameter name which will be used for generating the routes.
return 'foo';
}
/**
* Returns the fully qualified class name of a policy associated with the model.
*
* @return string|null
*/
public function getPolicy() : string
{
return MyPolicy::class;
}
/**
* Returns the attributes which are used for creating the model.
*
* @param Request $request
* @return array
*/
public function getCreateAttributes(Request $request) : array
{
return array_merge($request->all(), [
'user_id' => auth()->user->id,
];
}
/**
* Returns the attributes which are used to update the model.
*
* @param Request $request
* @return array
*/
public function getUpdateAttributes(Request $request) : array
{
return array_merge($request->all(), [
'user_id' => auth()->user->id,
];
}
}
Any Service Provider, eg. AppServiceProvider.php
$resource = new MyResource();
$registry = resolve(Weka\ApiKit\ResourceRegistry::class);
$resourceHandler = $registry->add($resource);
Available policy methods
Define the following methods in your Policy. If you're new to the concept of Policies, review the Laravel's Policy documentation.
use Illuminate\Contracts\Auth\Access\Authorizable;
public function index(Authorizable $user) : bool
{
// Return true or false
}
public function create(Authorizable $user) : bool
{
// Return true or false
}
public function read(Authorizable $user, Model $model) : bool
{
// Return true or false
}
public function update(Authorizable $user, Model $model) : bool
{
// Return true or false
}
public function delete(Authorizable $user, Model $model) : bool
{
// Return true or false
}
If guest users shoould be authorized, typehint the above $user
with null
:
use Illuminate\Contracts\Auth\Access\Authorizable;
public function index(Authorizable $user = null) : bool
{
// Return true or false
}
Displaying errors to users
In case you need to send and display errors to users, throw anywhere in the stack a subclass of \Weka\ApiKit\ResourceRegistry\RequestException
.
All subclasses of \Weka\ApiKit\ResourceRegistry\RequestException
will be catched and a response with your specified message will be sent.
Create an exception with your message first:
PriceMissingException.php
use \Weka\ApiKit\ResourceRegistry\RequestException;
class PriceMissingException extends RequestException
{
public function getUserMessage() : string
{
return 'You need to specify a price!';
}
}
Throw the previosuly created exception anywhere in the stack.
MyModel.php
class MyModel extends \Illuminate\Database\Eloquent\Model
{
protected static function boot()
{
parent::boot();
static::creating(function($model) {
if ($model->price === null) {
throw new PriceMissingException();
}
});
}
}
Usage within a resource:
MyResource.php
use Illuminate\Http\Request;
class MyResource extends \Weka\ApiKit\ResourceRegistry\AbstractResource
{
// ...
public function getCreateAttributes(Request $request) : array
{
if (!$request->has('price')) {
throw new PriceMissingException();
}
return array_merge($request->all(), [
'user_id' => auth()->user->id,
];
}
// ...
}
Events
You can subscribe to different events in a request lifecycle.
Just overload the proper methdos of \Weka\ApiKit\ResourceRegistry\AbstractResource
in your custom Resource.
Available methods / events are:
public function onIndex(Builder $builder, Request $request) : Builder;
public function onBeforeCreate(Model $model, Request $request) : Model;
public function onAfterCreate(Model $model, Request $request) : Model;
public function onRead(Model $model, Request $request) : Model;
public function onBeforeUpdate(Model $model, Request $request) : Model;
public function onAfterUpdate(Model $model, Request $request) : Model;
public function onBeforeDelete(Model $model, Request $request) : Model;
public function onAfterDelete(Model $model, Request $request) : Model;
Verifying your resource was added
To verify if your resource was added successfully run php artisan apikit:routes
in parent's app console.
Your resource with all its associated routes should be displayed in the console output.
Usage in Frontend
Webpack configuration
The base Model class is published in pure ES6+ and is not transpiled. You need to instruct webpack to transpile it when imported. Add the following lines to your webpack's configuration.
import path from 'path';
import fs from 'fs';
const APIKIT_DIR = path.resolve(fs.realpathSync('node_modules/@industry/apikit'));
export const config = {
entry: getEntryPoints(),
// ...
module : {
rules : [
{
test: /\.js$/,
include: path => {
return path.indexOf(APIKIT_DIR) > -1;
},
use: {
loader: 'babel-loader',
options: {
"presets": [
"@babel/preset-env",
]
},
}
},
// ...
]
},
// ...
};
Create your ES6 model class
import Model from '@industry/apikit';
class MyModel extends Model {
static getEndpoint() {
// Set this to the index route of your previously defined models.
return '/apikit/my-model';
}
}
That's it! You can now query the model as you wish.
MyModel.find(1)
fetches Model associated with primary key1
MyModel.query('foo')
queries models using anapiKitSearch
scope on server's model -- you need to implement that scope. See ApiKitSearchableTestModel for an example.MyModel.column('title', 'foo')
queries models usingWHERE title = 'foo'
MyModel.column('title', 'foo*')
queries models usingWHERE title LIKE 'foo%'
MyModel.scope('myScope', 'foo')
queries models using amyScope
scope on server's model -- you need to implement that scope. See ApiKitTestModel for an example.
To review all available methods, checkout the base Model class.
Custom setters & custom getters
// Your model
setFooAttribute(value) {
return value * value;
}
// model.attr().foo = 5; -> { foo: 25 }
getFirstNameAttribute(value) {
return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
}
// model.attr().first_name = 'nancy';
// console.log(model.attr().first_name) --> "Nancy".
You can cast your values with setters:
setIdAttribute(value) {
value = Number(value);
if (isNaN(value)) {
return null;
}
return value;
}