PHPackages                             aryelgois/medools-router - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. aryelgois/medools-router

ActiveLibrary[API Development](/categories/api)

aryelgois/medools-router
========================

A Router framework to bootstrap RESTful APIs based on aryelgois/Medools

v0.3.3(7y ago)1421MITPHPPHP ^7.0

Since Mar 31Pushed 7y agoCompare

[ Source](https://github.com/aryelgois/medools-router)[ Packagist](https://packagist.org/packages/aryelgois/medools-router)[ RSS](/packages/aryelgois-medools-router/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (7)Dependencies (4)Versions (9)Used By (1)

Medools Router
==============

[](#medools-router)

Index:

- [Intro](#intro)
- [Routes](#routes)
    - [Root Route](#root-route)
- [Install](#install)
- [Setup](#setup)
    - [Configurations](#configurations)
    - [Resources list](#resources-list)
- [Usage](#usage)
- [HTTP Methods](#http-methods)
- [Query parameters](#query-parameters)
    - [Collection request](#collection-request)
    - [Resource request](#resource-request)
    - [Collection or Resource](#collection-or-resource)
- [Cache](#cache)
- [Authentication and Authorization](#authentication-and-authorization)
- [Errors](#errors)
- [Extending](#extending)
- [Changelog](CHANGELOG.md)

Intro
=====

[](#intro)

A Router framework to bootstrap RESTful APIs based on [Medools](https://github.com/aryelgois/Medools).

Routes
======

[](#routes)

Basically, all routes are formatted as:

- `/resource`: Requests a collection of resources
- `/resource/id`: Requests a specific resource

[See bellow](#resources-list) details about the resource name.

The resource `id` depends on the [PRIMARY\_KEY](https://github.com/aryelgois/Medools#primary_key) defined in the model. Usually it is an integer, but if there is a composite `PRIMARY_KEY`, each column must be in the `id`, separated by `primary_key_separator`, e.g. `/resource/1-1`.

Also, resources can be nested:

- `/resource/id/resource_1`: Requests a collection of `resource_1` with `resource(id)` in the appropriate foreign column\*
- `/resource/id/resource_1/offset`: Requests a specific resource from the collection in the previous topic. `offset` works as `collection[offset - 1]`, i.e. it **is not** `resource_1`'s id, and has 1-based index

Nesting is only allowed in resources (not in collections: `/resource/resource_1`is wrong), but the route can end in a collection. And there is no hard limit of nesting levels, the only requirement is that the last resource (or collection) has a foreign key to the previous resource, that has a foreign key to the previous one, and so on. The `id` is only used in the first resource.

> \* If `resource_1` has multiple foreign columns for `resource`, only the first one is used. As a work around, you can use [Collection query parameters](#collection-request) to filter the correct column

Root Route
----------

[](#root-route)

When requesting the `/` route, a count of all resources is returned. If the `meta` config is not empty, it is also included. An `OPTIONS` request just lists all implemented methods.

Install
=======

[](#install)

Open a terminal in your project directory and run:

`composer require aryelgois/medools-router`

Setup
=====

[](#setup)

[Medools](https://github.com/aryelgois/Medools) requires a config file to connect to your database. [(see more here)](https://github.com/aryelgois/Medools#setup)

Additionally, you will need some configurations and a resources list for your API.

Also, if using authentication, you will need a secret file (or multiple files) and a Sign up system (or an administrator will add valid credentials).

Configurations
--------------

[](#configurations)

The Router accepts an array of configurations that will be passed to properties in it. The array may contain:

- `always_cache` *(boolean)*: If `GET` and `HEAD` requests always check cache headers (default: `true`)
- `always_expand` *(boolean)*: If foreign models are always expanded in a `GET`resource request (default: `false`)
- `authentication` *(mixed\[\]|null)*: Specify how to authenticate the requests. If `null`, the authentication is disabled. If set, can contain the keys:

    - `secret` *(string)* **required**: Path to secret used to sign the [JWT](https://github.com/firebase/php-jwt) tokens. It can be a file or a directory

        If it is a directory, the Router expects it to contain secrets named after their expiration time, in a unix timestamp. Only the first file is used, and the tokens expire at maximum in the same stamp as the secret. You can use a cron job to keep generating more secrets

        > **DO NOT** keep the secret in your web server's public directory, neither let git track it
    - `realm` *(string)*: Sent in `WWW-Authenticate` Header
    - `algorithm` *(string)*: Hash algorithm used to sign the tokens (default: `HS256`)
    - `claims` *(mixed\[\])*: Static JWT claims to be included in every token. Some claims are already defined by the Router: `iss`, `iat`, `exp` and `user`
    - `expiration` *(int)*: Expiration duration (in seconds) used to calculate the `exp` claim. It is limited by the secret timestamp (when `secret` is a directory)
    - `verify` *(boolean)*: If the authentication's email must be verified (default: `false`)
- `cache_method` *(string)*: Function used to hash the Response Body, for HTTP caching `GET` and `HEAD` requests. It receives a string with the body serialized (default: `md5`)
- `default_content_type` *(mixed\[\])*: Default content type for `GET` requests. It is combined with resource's `GET` handlers

    The default is `application/json` with internal handlers
- `default_filters` *(string|string\[\]|null)*: Default value for resources' filters. See more in [Resources list](#resources-list) `filters` option
- `default_publicity` *(boolean|string\[\]|string)*: Default value for resources' `public` option

    - `false`: All resources are private by default. It only has effect if `authentication` is not `null`
    - `true`: All resources are public by default. It has the same effect as not defining `authentication`, but if it is defined, this value is useful to make most resources public and some private
    - `string[]`: List of methods that are always public
    - `string`: The same as an array with one item
- `extensions` *(string\[\])*: Map of file extensions and a related content type

    It allows overriding browser's `Accept` header with an extension in the URL. Fill it with extensions for custom content types your resources use

    Note that it is only checked by safe methods and is only useful to resources that define custom `GET` handlers. Using unknown extensions or when they are not expected may invalidate the route
- `implemented_methods` *(string\[\])*: List of HTTP methods implemented by the API. You can limit which methods can be used, but to add more methods you would need to extend or modify the Router class
- `meta` *(mixed\[\]|null)*: Contains information to be included in the response for the [root route](#root-route). You can use it to add links to your project or documentation
- `per_page` *(integer)*: Limit how many resources can be returned in a collection request. If `0`, no pagination is done (default: `20`)
- `primary_key_separator` *(string)*: Separator used in composite [PRIMARY\_KEY](https://github.com/aryelgois/Medools#primary_key) (default: `-`)

    It does not need to be a single character, since it is used as [explode](http://php.net/manual/en/function.explode.php)delimiter. But it must not be contained in the id itself, and can not contain forward slash `/`
- `zlib_compression` *(boolean)*: If should enable zlib compression when appropriate (default: `true`)

Resources list
--------------

[](#resources-list)

A map of resource names to Medools Models, and optionally to specific configurations for that resource.

The resource names should be plural. You should choose the casing convention to be `camelCase` or `snake_case`. Prefer keeping consistent with the columns casing in your models.

The value mapped can be either a string with the Fully Qualified Model Class, or an array with:

- `model` *(string)* **required**: Fully Qualified Model Class
- `methods` *(string|string\[\])*: Allowed HTTP methods. Defaults to `implemented_methods`. `OPTIONS` is implicitly included
- `handlers` *(mixed\[\])*: Map of HTTP methods to php functions that process the Request

    - Several levels of arrays are allowed but not required, in the order: `HTTP method => Content type => Resource kind` (resource or collection). The leaves must be the function names
    - These functions receive a `Resource` and must be capable of generating all the output (both Headers and Body) for the Response. Exceptions are catched by the Router
    - The same handlers for `GET` are used for `HEAD` requests, and a `HEAD` key is ignored. Content types for `GET` are related to the accepted Response, while other methods use them with the Request's payload
    - `GET` content types enable their related extensions in the route endpoint
    - All methods implicitly have an internal `application/json` handler. If a method defines a single handler (i.e. a `string`) or if a `application/json`key is set, the internal handler is disabled for that method (unless using the magic value `__DEFAULT__`)
    - When defining a single Resource kind (or setting one to `null`), requesting the disabled one is not acceptable
- `filters` *(string|string\[\])*: List of columns that can be used as query parameters to filter collection requests. It also accepts a string of a special fields group ([see fields query parameter](#collection-or-resource)), or `ALL` to allow filtering on any column. It replaces the `default_filters`config
- `cache` *(boolean)*: If caching headers should be sent. It overrides the `always_cache` config
- `max_age` *(int)*: `Cache-Control` max-age (seconds). Tells how long the cache is considered fresh until it becomes stale and the client needs to validate with a request to the server
- `public` *(boolean|string\[\]|string)*: Behaves like `default_publicity`, and is only used if `authentication` config is set

Usage
=====

[](#usage)

Follow the [example](example) to see how to use this framework in your web server.

When a request is done, your web server must redirect to a php script. If you are using Apache, there is already a `.htaccess` in the example that does the job.

First, the script requires Composer's autoloader and loads Medools config. Then it gathers request data:

- `Method`: A HTTP method
- `URI`: Requested route. It must not contain the path to the api directory, but query parameters must remain in the URI
- `Headers`: Headers in the request. Used headers are:

    - `Authorization`: Contains credentials for authenticating the request. Possible types are Basic and Bearer
    - `X-HTTP-Method-Override`: If your clients can not work with `PUT`, `PATCH`or `DELETE`, they can use it to replace `POST` method. It is enabled by `ENABLE_METHOD_OVERRIDE`
    - `Content-Type`: Data in the request payload is expected to be `application/json` by default. Resources may specify more types they read with external handlers
    - `Accept`: The Router responses are, by default, in `application/json`. But resources may define more types they output, associated to external handlers
    - `If-None-Match`: If caching headers are enabled, it is checked to see if a stale cache can still be used
- `Body`: Raw data from the payload that will be parsed
- `URL`: URL that access the Router. It is used to create links to other resources

Finally, a Router object is created with a [resources list](#resources-list), optional [configurations](#configurations) and all that data from the request. It will do its best to solve the route and give a response.

The resources list and configurations can be stored in a external file, like `.json` or `.yml` or something else. Remember to add a library to parse that file before passing to the Router.

You can also configure a subdomain like `api.example.com` to handle the routes. It is up to you.

> **SECURITY NOTE**: It is highly recommended that you use SSL to protect your data transactions

HTTP Methods
============

[](#http-methods)

The following HTTP methods are implemented by the Router class:

- `GET`: By default, responses contain a JSON representation of the requested route

    - Resource requests include a `Link` header listing the location of foreign models
    - Collection requests include a `Link` header for pagination and a `X-Total-Count` counting all resources (ignoring pagination, but considering filters), unless `per_page` is 0
    - Also, caching headers are sent, if enabled

    Different content types can be [configured per resource](#resources-list) and it is chosen based on request's `Accept` header. They will not send the headers listed previously (unless the external handler sends by itself)
- `HEAD`: Does the same processing for `GET`, but only send headers (even for external handlers)
- `OPTIONS`: Lists allowed methods for the requested route in `Allow` header. It is a special method that is always allowed, if implemented
- `POST`: Creates a new resource inside the collection with data in the request payload. A route to the new resource is in `Location` header. This method is not allowed for resource routes
- `PATCH`: Sets one or more columns to a specific value, then responses with a `GET` of the current state
- `PUT`: Replaces the requested resource with data in the request payload (all required columns must be passed), or create a new one if it does not exist. The response is a `GET` of the current state. This method is not allowed for collection routes

    The `PRIMARY_KEY` columns can be omitted if they are in the route, i.e. a route without nested resources. In this case, the same columns in the payload have preference over the route
- `DELETE`: Removes the resource or collection's resources from the Database

Some notes:

- Pagination is disabled by default for a collection `PATCH` or `DELETE`, but the client can explicitly use it with `page` or `per_page` query parameters
- A `Content-Location` Header can be sent, implying an updated or better route to the requested entity

Query parameters
================

[](#query-parameters)

The client can use some query parameters for a more precise request:

Collection request
------------------

[](#collection-request)

- `sort`: List of comma separated columns to be sorted. A possible unary negative implies descending sort order

    Example: `?sort=-priority,stamp` sorts in descending order of priority. Within a specific priority, older entries are ordered first
- `page`: Collection page the client wants to access. It has 1-based index
- `per_page`: An integer limiting how many entries should be returned. It overrides `per_page` config. As syntax sugar, `all` is the same as `0`
- filters: Defined in [resources list](#resources-list), allows a fine control of the collection. Additional operators can make advanced filters:

OperatorMedoo counterpartNamegt&gt;Greater Thange&gt;=Greater or Equal tolt&lt;Lesser Thanle&lt;=Lesser or Equal tone!Not Equalbw&lt;&gt;BetWeennw&gt;&lt;Not betWeenlk~LiKenk!~Not liKerxREGEXPRegeXpExamples:

- A simple string represents a list of comma separated values:

    `?id=1,2,3` → `['id' => ['1', '2', '3']]`
- A numeric array can also be used:

    `?filter[]=1&filter[]=2&filter[]=foo,bar` → `['filter' => ['1', '2', 'foo,bar']]`

    Helps including a comma in the item itself
- `bw` and `nw` accept two values separated by comma. `ne`, `lk` and `nk` accept one or more values separated in the same way:

    `?id[bw]=100,200` → `['id[]' => ['100', '200']]`
- They also accept their multiple values in an array:

    `?filter[ne][]=1&filter[ne][]=2&filter[ne][]=foo,bar` → `['filter[!]' => ['1', '2', 'foo,bar']]`
- Passing `NULL` to `ne` or directly to the filter will work:

    `?active=NULL` → `['active' => null]`

    `?active[ne]=NULL` → `['active[!]' => null]`

Notes:

- Filters must be enabled by `default_filters` or in resource's `filters`
- Filters affect the pagination and the `X-Total-Count` Header
- Avoid columns with the same name as other query parameters

Resource request
----------------

[](#resource-request)

- `expand`: Its existence forces foreign models to be expanded, unless its value is `false`, which is useful if `always_expand` is `true`

Collection or Resource
----------------------

[](#collection-or-resource)

- `fields`: List of comma separated columns to include in the response. It also limits which foreign models are listed in `Link` Header, if not expanding them

    There are special fields group that are replaced by the corresponding resource columns. They have the same names as the Medools constants:

    - `PRIMARY_KEY`
    - `AUTO_INCREMENT`
    - `STAMP_COLUMNS`
    - `OPTIONAL_COLUMNS`
    - `FOREIGN_KEYS`
    - `SOFT_DELETE`

    Notes:

    - Omitting or giving an empty `fields` parameter results in all fields in the response. But passing a query that solves to nothing (like `?fields=FOREIGN_KEYS` in a resource without foreigns, or simply `?fields=,`) results in no fields being sent
    - It does not affect the order the fields are sent *(also, in JSON objects are unordered)* and repeated fields (maybe because of the groups) are ok

Cache
=====

[](#cache)

If enabled, caching headers `ETag` and `Cache-Control` are sent with successful responses that have a body.

This functionality can be enabled globally or per resource.

If the client sends `If-None-Match` Header, it is tested to provide a `304 Not Modified` response.

Authentication and Authorization
================================

[](#authentication-and-authorization)

By default, all resources are public. Defining the `authentication` config makes all resources private and to access them the client must authenticate. In this case, some resources may define themselves as public, or allow a few methods for unauthenticated requests.

To authenticate, first the client must provide some credentials to the Router with a Basic `Authorization` Header. In the [example](example), it is at the `/auth`route. If authenticated, the server sends back a JWT token. This token must be sent to all the other routes with a Bearer `Authorization` Header until it expires. When it happens, the client must authenticate again.

Authentication credentials ("Accounts") are stored in the `authentications`table, with the columns:

- `id`: Unique id sent in the token
- `username`: Because of HTTP Basic Authentication limitations, it must not contain the colon character (`:`)
- `password`: Contains an encrypted representation of the account's password
- `email`: For contacting the account user
- `verified`: Tells if the email was verified
- `enabled`: Useful to block an account but keep its data
- `update` and `stamp`: Just some timestamps

Another table, `authorizations`, maps accounts to authorized resources. It can also list authorized methods and can include a filter passed directly to Medoo, to limit which entries are authorized.

These two tables are be filled by your Sign up system, and controlled by an user panel or an administrator.

Errors
======

[](#errors)

When possible, the Router will return an error response with an appropriate HTTP Status code and a JSON payload with `code` and `message` keys. Other keys may appear, but are optional.

Error table:

CodeNameStatusDescription0`ERROR_UNKNOWN_ERROR`500Unknown error placeholder1`ERROR_INTERNAL_SERVER`500The Router detected a Server error2`ERROR_INVALID_CREDENTIALS`401Authorization Header is invalid3`ERROR_UNAUTHENTICATED`401Request could not be authenticated4`ERROR_INVALID_TOKEN`401Authorization token is invalid5`ERROR_METHOD_NOT_IMPLEMENTED`501Requested Method is not implemented in the Router6`ERROR_UNAUTHORIZED`403Requested resource is not public and the client does not have authorization to access it7`ERROR_INVALID_RESOURCE`400Route contains invalid resource8`ERROR_INVALID_RESOURCE_ID`400Route contains invalid resource id9`ERROR_INVALID_RESOURCE_OFFSET`400Route contains invalid resource offset10`ERROR_INVALID_RESOURCE_FOREIGN`400Nested resource does not have foreign key to previous resource11`ERROR_RESOURCE_NOT_FOUND`404Requested resource does not exist in the Database12`ERROR_UNSUPPORTED_MEDIA_TYPE`415Request's Content-Type is not supported13`ERROR_INVALID_PAYLOAD`400Request's Content-Type has syntax error or is invalid14`ERROR_METHOD_NOT_ALLOWED`405Requested Method is not allowed for Route15`ERROR_NOT_ACCEPTABLE`406Requested resource can not generate content complying to Accept header16`ERROR_INVALID_QUERY_PARAMETER`400Query parameter has invalid data17`ERROR_UNKNOWN_FIELDS`400Resource does not have requested fields18`ERROR_READONLY_RESOURCE`400Resource can not be modified19`ERROR_FOREIGN_CONSTRAINT`400Foreign constraint failsIf an error or exception was not handled correctly, the response body is unpredictable and may depend on [error\_reporting](http://php.net/manual/en/function.error-reporting.php).

Extending
=========

[](#extending)

You can extend the Router class to hack its code. Just remember to pass the correct class to the Controller.

[Changelog](CHANGELOG.md)
=========================

[](#changelog)

###  Health Score

25

—

LowBetter than 37% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity9

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~18 days

Recently: every ~12 days

Total

7

Last Release

2855d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/0f95855ecc3be665cdac87d201a24f763b0abb9c0fea4297b5f0bab4bff5e3b0?d=identicon)[aryelgois](/maintainers/aryelgois)

---

Top Contributors

[![aryelgois](https://avatars.githubusercontent.com/u/21041013?v=4)](https://github.com/aryelgois "aryelgois (224 commits)")

---

Tags

apimedoolsrestfulapirestfulmedools

### Embed Badge

![Health badge](/badges/aryelgois-medools-router/health.svg)

```
[![Health](https://phpackages.com/badges/aryelgois-medools-router/health.svg)](https://phpackages.com/packages/aryelgois-medools-router)
```

###  Alternatives

[docusign/esign-client

The Docusign PHP library makes integrating Docusign into your apps and websites a super fast and painless process. The library is open sourced on GitHub, look for the docusign-esign-php-client repository. Join the eSign revolution!

2087.4M13](/packages/docusign-esign-client)[nezamy/route

Route - Fast, flexible routing for PHP, enabling you to quickly and easily build RESTful web applications.

21436.6k5](/packages/nezamy-route)[teepluss/api

Laravel 4 Internal Request (HMVC)

7034.0k](/packages/teepluss-api)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
