aryelgois / medools-router
A Router framework to bootstrap RESTful APIs based on aryelgois/Medools
Requires
- php: ^7.0
- ext-zlib: *
- aryelgois/medools: ^5.1
- aryelgois/utils: ^0.5
- aryelgois/yasql-php: ^0.6.0
- firebase/php-jwt: ^5.0
This package is not auto-updated.
Last update: 2024-11-10 05:46:00 UTC
README
Index:
- Intro
- Routes
- Install
- Setup
- Usage
- HTTP Methods
- Query parameters
- Cache
- Authentication and Authorization
- Errors
- Extending
- Changelog
Intro
A Router framework to bootstrap RESTful APIs based on Medools.
Routes
Basically, all routes are formatted as:
-
/resource
: Requests a collection of resources -
/resource/id
: Requests a specific resource
See bellow details about the resource name.
The resource id
depends on the 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 ofresource_1
withresource(id)
in the appropriate foreign column* -
/resource/id/resource_1/offset
: Requests a specific resource from the collection in the previous topic.offset
works ascollection[offset - 1]
, i.e. it is notresource_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 forresource
, only the first one is used. As a work around, you can use Collection query parameters to filter the correct column
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
Open a terminal in your project directory and run:
composer require aryelgois/medools-router
Setup
Medools requires a config file to connect to your database. (see more here)
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
The Router accepts an array of configurations that will be passed to properties in it. The array may contain:
-
always_cache
(boolean): IfGET
andHEAD
requests always check cache headers (default:true
) -
always_expand
(boolean): If foreign models are always expanded in aGET
resource request (default:false
) -
authentication
(mixed[]|null): Specify how to authenticate the requests. Ifnull
, the authentication is disabled. If set, can contain the keys:-
secret
(string) required: Path to secret used to sign the JWT tokens. It can be a file or a directoryIf 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 inWWW-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
anduser
-
expiration
(int): Expiration duration (in seconds) used to calculate theexp
claim. It is limited by the secret timestamp (whensecret
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 cachingGET
andHEAD
requests. It receives a string with the body serialized (default:md5
) -
default_content_type
(mixed[]): Default content type forGET
requests. It is combined with resource'sGET
handlersThe default is
application/json
with internal handlers -
default_filters
(string|string[]|null): Default value for resources' filters. See more in Resources listfilters
option -
default_publicity
(boolean|string[]|string): Default value for resources'public
option-
false
: All resources are private by default. It only has effect ifauthentication
is notnull
-
true
: All resources are public by default. It has the same effect as not definingauthentication
, 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 typeIt allows overriding browser's
Accept
header with an extension in the URL. Fill it with extensions for custom content types your resources useNote 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. 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. If0
, no pagination is done (default:20
) -
primary_key_separator
(string): Separator used in composite PRIMARY_KEY (default:-
)It does not need to be a single character, since it is used as explode 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
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 toimplemented_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 forHEAD
requests, and aHEAD
key is ignored. Content types forGET
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. astring
) or if aapplication/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), orALL
to allow filtering on any column. It replaces thedefault_filters
config -
cache
(boolean): If caching headers should be sent. It overrides thealways_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 likedefault_publicity
, and is only used ifauthentication
config is set
Usage
Follow the 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 withPUT
,PATCH
orDELETE
, they can use it to replacePOST
method. It is enabled byENABLE_METHOD_OVERRIDE
-
Content-Type
: Data in the request payload is expected to beapplication/json
by default. Resources may specify more types they read with external handlers -
Accept
: The Router responses are, by default, inapplication/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, optional 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
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 aX-Total-Count
counting all resources (ignoring pagination, but considering filters), unlessper_page
is 0 -
Also, caching headers are sent, if enabled
Different content types can be configured per resource 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 forGET
, but only send headers (even for external handlers) -
OPTIONS
: Lists allowed methods for the requested route inAllow
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 inLocation
header. This method is not allowed for resource routes -
PATCH
: Sets one or more columns to a specific value, then responses with aGET
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 aGET
of the current state. This method is not allowed for collection routesThe
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
orDELETE
, but the client can explicitly use it withpage
orper_page
query parameters -
A
Content-Location
Header can be sent, implying an updated or better route to the requested entity
Query parameters
The client can use some query parameters for a more precise request:
Collection request
-
sort
: List of comma separated columns to be sorted. A possible unary negative implies descending sort orderExample:
?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 overridesper_page
config. As syntax sugar,all
is the same as0
-
filters: Defined in resources list, allows a fine control of the collection. Additional operators can make advanced filters:
Examples:
-
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
andnw
accept two values separated by comma.ne
,lk
andnk
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
tone
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'sfilters
-
Filters affect the pagination and the
X-Total-Count
Header -
Avoid columns with the same name as other query parameters
Resource request
expand
: Its existence forces foreign models to be expanded, unless its value isfalse
, which is useful ifalways_expand
istrue
Collection or Resource
-
fields
: List of comma separated columns to include in the response. It also limits which foreign models are listed inLink
Header, if not expanding themThere 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
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
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, 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
andstamp
: 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
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:
If an error or exception was not handled correctly, the response body is unpredictable and may depend on error_reporting.
Extending
You can extend the Router class to hack its code. Just remember to pass the correct class to the Controller.