npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@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 key 1
  • MyModel.query('foo') queries models using an apiKitSearch scope on server's model -- you need to implement that scope. See ApiKitSearchableTestModel for an example.
  • MyModel.column('title', 'foo') queries models using WHERE title = 'foo'
  • MyModel.column('title', 'foo*') queries models using WHERE title LIKE 'foo%'
  • MyModel.scope('myScope', 'foo') queries models using a myScope 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;
}