Warp API
Warp API (formerly known as Warp Server) is an express
middleware that implements an easy-to-use API for managing and querying data from your database.
Currently, Warp API
uses mysql@5.7
as its database of choice, but can be extended to use other data storage providers.
Table of Contents
Installation
To install Warp API, simply run the following command:
npm install --save warp-api
Configuration
Warp API is built on top of express
and can be initialized in any express
project. To do so, simply add the following configruation to the main file of your project:
// References;; // Create a new Warp API APIvar api = apiKey: 'someLongAPIKey123' masterKey: 'someLongMasterKey456' databaseURI: 'mysql://youruser:password@yourdbserver.com:3306/yourdatabase'; // Initialize the apiapi; // Apply the Warp API router to your preferred base URLvar app = ;app;
NOTE: Warp API uses modern node features. We recommend using TypeScript as your transpiler. If you are transpiling via Babel, be sure to target at least Node 6.
Sample .babelrc
Configuration Options
Warp API accepts several configuration options that you can fully customize.
Name | Description |
---|---|
apiKey | API key for your Warp API (required) |
masterKey | Master key for your Warp API (required), NOTE: Only super users should know this! |
databaseURI | URI of your database (protocol://user:password@server:port/database) |
requestLimit | Number of requests allowed per second (default: 30) |
sessionDuration | Validity duration of a logged in user's session (default: '2 years') |
keepConnections | Determine whether db pool connections are kept alived or auto-disconnected (boolean) |
charset | Charset encoding of database queries (default: 'utf8mb4_unicode_ci') |
passwordSalt | Customize the password salt for log-ins (default: 8) |
customResponse | Determine whether the response is going to be handled manually or automatically |
supportLegacy | Support legacy features such as className instead of class_name |
Classes
A Class
is a representation of a table
inside a database. By defining a class, you can determine how fields, known as Keys
in Warp, are parsed and formatted.
For example, a dog
table will have a corresponding class called Dog
that has different Keys
such as name, age, and weight.
Among these Keys
are three special ones that are automatically set by the server and cannot be manually edited.
id
: a unique identifier that distinguishes an object inside a tablecreated_at
: a timestamp that records the date and time when an object was created (UTC)updated_at
: a timestamp that records the date and time when an object was last modified (UTC)
NOTE: Be sure to have
id
,created_at
,updated_at
, anddeleted_at
fields on your table in order for Warp API to work with them.
Creating a Class
To create a Class, simply extend from WarpAPI.Class
.
// Import Class from WarpAPI; static { return 'dog'; } static { return 'name' 'age' 'weight'; }
From the example above you can see a couple of properties that you need to declare in order for your Class to work.
First, you need to declare the table that the Class is representing. For this, you create a static getter called className
that returns the name of the table.
Then, you need to decalre the Keys that the table is composed of. For that, you create a static getter called keys
, which returns an array of their names. Note that you do not include id, created_at, and updated_at keys in this area.
If this were shown as a table, it would look similar to the following.
Table: Dog
id | name | age | weight | created_at | updated_at |
---|---|---|---|---|---|
1 | Bingo | 4 | 33.2 | 2018-03-09 12:38:56 | 2018-03-09 12:38:56 |
2 | Ringo | 5 | 36 | 2018-03-09 12:38:56 | 2018-03-09 12:38:56 |
Using an Alias
If you need to make an alias for your table in situations where the table name is not suitable, you can define the alias as the className
and then declare a static getter source
returning the real name of the table.
// Import Class from Warp API; static { return 'bird'; } static { return 'avian'; } static { return 'name' 'age' 'weight'; }
Using Pointers
For relational databases like MySQL, a foreign key is a fairly common concept. It represents a link to another table that acts as its parent.
For example, a dog
table can have a location_id
foreign key that points to the location
table. In terms of Warp, this would mean that the dog
class would have a pointer to the location
class.
// Import Class from WarpAPI; static { return 'location'; } static { return 'city' 'country'; } static { return 'dog'; } static { return 'name' 'age' 'weight' Location; }
In the above example, you can see that a new key
has been added to dog
, called location
.
We use the extended class Location
in order to create a new key via the .as()
method. This means that for our endpoints, we can now interact with the dog
's location using the alias location
.
For more information about endpoints, visit the Using the API section.
Secondary Pointers
If you also want to include a pointer from another pointer, you can do so via the .from()
method. For example, if location
had a pointer to a country
class, and you want to include that to your dog
class, you would do the following.
// Import Class from WarpAPI; static { return 'country'; } static { return 'location'; } static { return 'city' Country; } static { return 'dog'; } static { return 'name' 'age' 'weight' Location Country ; }
NOTE: Make sure the secondary pointer is declared after its source pointer
Now that you've defined the secondary pointer, you can now retrieve it via the endpoints. Note that you cannot manually set the value of a secondary pointer, you can only retrieve it.
Defining Key Types
By default, Warp tries to save the values that you passed to the keys as-is. That means that there is no validation or parsing being done before it is sent to the database. In some cases, this would be fine. However, you may opt to define the keys' data types.
To define key types, use the Key()
function from Warp API.
// Import Class and Key from WarpAPI; static { return 'dog'; } static { return 'name' ; }
Data Types
Name | Parameters | Description |
---|---|---|
asString | minLength ?: number, maxLength ?: number |
Declare the key as a string |
asDate | Declare the key as a date | |
asNumber | min ?: number, max ?: number |
Declare the key as a number, allows the use of Increment |
asInteger | min ?: number, max ?: number |
Declare the key as an integer, allows the use of Increment |
asFloat | decimals ?: number, min ?: number , max? : number |
Declare the key as a float, allows the use of Increment |
asJSON | *only available in MySQL 5.7+ | Declare the key as JSON, allows the use of SetJson and AppendJson |
Increment
If a value has been defined as either a number
, an integer
, or a float
, then its value can be incremented and decremented using a relative value. That means if the current value is 5
, for example, then incrementing by 1
will turn its value to 6
. Incrementing by -1
, on the other hand, will turn its value to 4
.
JSON
For databases that support JSON data types (MySQL 5.7+), Warp API has reserved functions that directly manipulate the JSON values on the database. They are SetJson
and AppendJson
. By giving the Key name, the path you are trying to manipulate (See MySQL JSON docs for more information), and a value, you can easily set
or append
data into a table without the need to parse and format manually.
For more information about specials (Increment
, SetJson
, AppendJson
), visit the Using the API section.
Setters and Getters
If the provided Key types are not suitable for your needs, you can manually define setters and getters for your keys.
To define a setter, simply use the camelCase
version of the key as its name.
// Import Class from WarpAPI; static { return 'dog'; } static { return 'name' 'age' 'weight' 'dog_years'; } { ifvaluelength < 5 throw 'Dog years must be at least 5'; this; }
From the above example, you can see that you can throw an error when a value is not valid. Also, you can use the pre-built .set()
method in order to set the value for the Object. Without it, the value will not be saved in the database.
Similar to the setter, you can define a getter by simply using the camelCase
version of the key as its name.
// Import Class from WarpAPI; static { return 'dog'; } static { return 'name' 'age' 'weight' 'dog_years'; } { ifvalue < 5 throw 'Dog years must be at least 5'; this; } { return this + ' years'; }
By using the .get()
method, you can retrieve the data that was stored and present it back to the response in a different format.
Before Save
Additionally, if you need to manipulate the data before it is saved to the database, you can declare a beforeSave()
method.
// Import Class from WarpAPI;; static { return 'dog'; } static { return 'name' 'age' 'weight'; } { return this * 22; } async { // isNew informs you whether the data being saved is new or just an update ifthisisNew // The request is trying to create a new object else // The request is trying to update an existing object // You can use .get() as well as getters ifthis > 5 && thisweight > 120 this; // Throw an error to prevent the object from being saved ifthis > 10 && thisweight < 90 throw 'The provided age and weight are not logical'; // Await a promise before saving await somePromise; return; }
After Save
If you need to manipulate the data after it is saved to the database, you can declare an afterSave()
method. Note that works in the background after the response has been sent. Thus, anything returned or thrown in this area does not affect the response.
// Import Class from WarpAPI;; static { return 'dog'; } static { return 'name' 'age' 'weight'; } { return this * 22; } async { // isNew informs you whether the data saved was new or just an update ifthisisNew // The request created a new object else // The request updated an existing object // Throwing an error does nothing to the response throw 'The provided age and weight are not logical'; }
Before Destroy
If you need to manipulate the data before it is destroyed in the database, you can declare a beforeDestroy()
method.
// Import Class from WarpAPI; static { return 'dog'; } static { return 'name' 'age' 'weight'; } { return this * 22; } async { // The only key available before destroy is the `id` key const age = this; // === undefined // Throw an error to prevent the object from being destroyed ifthisid === 10 throw 'You cannot delete id 10!'; // Await a promise before destroying await somePromise; return; }
After Destroy
If you need to manipulate the data after it has been destroyed from the database, you can declare an afterDestroy()
method. Note that works in the background after the response has been sent. Thus, anything returned or thrown in this area does not affect the response.
// Import Class from WarpAPI; static { return 'dog'; } static { return 'name' 'age' 'weight'; } { return this * 22; } async { // Throwing an error does nothing to the response throw 'You cannot delete a dog!'; }
Adding the Class
Right now, the Class you created is still not recognized by your Warp API. To register its definition, use .classes.add()
.
// Add the Dog classapiclasses; // Apply the router afterapp;
.classes.add()
accepts a mapping of Classes, so you can do the following.
// Add multiple classesapiclasses; // Apply the router afterapp;
Now that you've added the Classes, once you start the server, you can begin accessing them via the /classes
endpoint.
> curl -H 'X-Warp-API-Key=1234abcd' http://localhost:3000/api/1/classes/dog
For more information about endpoints, visit the Using the API section.
Authentication
User authentication is a common concern for applications. Luckily, for Warp API, this comes built-in. Aside from regular classes, there are two other special classes that make up the authentication layer of Warp.
Creating a User class
A User
represents individual people who log in and make requests to the server. To enable this feature, you would need to declare a new class which extends from User
.
// Import User from WarpAPI; static { return 'user'; }
By default, the User class already has pre-defined keys that are important for it to function properly.
username
: A unique identifier created by the useremail
: A unique email identified for the userpassword
: An encrypted string that gets stored and validated during log-ins
These keys are required and your table must have columns defined for each. If, however, you declared these fields with different names in your table, you can opt to declare their corresponding names via the following methods.
// Import User from WarpAPI; static { return 'user'; } static { return 'unique_name'; } static { return 'email_address'; } static { return 'secret_key'; }
If you also want to place additional keys from your user table, you can do so by extending the class' super.keys
.
// Import User from WarpAPI; static { return 'user'; } static { return ...super'keys' 'first_name' 'last_name' 'display_photo'; // If you do not have support for spread operators, use the following: // const keys = super.keys; // return keys.concat('first_name', 'last_name', 'display_photo'); }
Creating a Session class
A Session
represents a successful authentication of a user. They are created every time a user logs in, and destroyed every time they are logged out. For Warp API, a Session's sessionToken
is often used to make requests to the server. This sessionToken is validated and returned as the currentUser
of the request. You can find more about this in the Sessions section.
To enable this feature, you would need to declare a new class which extends from Session
.
// Import Session from WarpAPI; static { return 'session'; }
By default, like the User class, the Session class already has pre-defined keys that are important for it to function properly.
session_token
: A unique token every time a successful login occursorigin
: The origin of the request (js-sdk
,android
,ios
)revoked_at
: Date until the session expires
These keys are required and your table must have columns defined for each. If, however, you declared these fields with different names in your table, you can opt to declare their corresponding names via the following methods.
// Import Session from WarpAPI; static { return 'user'; } static { return 'session_key'; } static { return 'requested_by'; } static { return 'expires_at'; }
If you also want to place additional keys from your user table, you can do so by extending the class' super.keys
.
// Import Session from WarpAPI; static { return 'session'; } static { return ...super'keys' 'ip_address' 'fcm_key'; // If you do not have support for spread operators, use the following: // const keys = super.keys; // return keys.concat('ip_address', 'fcm_key'); }
Setting the Auth Classes
To set the newly defined auth classes, use the .auth.set()
methods
// Set auth classesapiauth; // Apply the router afterapp;
Functions
Ideally, you can perform a multitude of tasks using classes. However, for special operations that you need to perform inside the server, you can use Functions
.
A Function
is a piece of code that can be executed via a named endpoint. It receives input keys
that it processes in order to produce a result.
Create a Function
To create a Function, create a new class that extends the Function
.
// Import Function from Warp API;; static { return 'get-favorite-dogs'; } static { return false; } async { // collection_id was passed to the request const collectionID = this; // Do some work here... const favoriteDogs = await ; // Throw an error instead of a result throw 'Cannot get your favorite dogs'; // Return the result return favoriteDogs; }
For the above example, you can see that you need to declare a functionName
getter as well as a run()
method. These are the only two things you need in order to create a function.
However, you might notice the masterOnly
getter declared atop. What does this does is just basically limits access to the function to masters (i.e. requests made using the X-Warp-Master-Key
). You can omit this code, and by default is set to be false
.
Adding a Function
Right now, the Function you created is still not recognized by your Warp API. To register its definition, use .functions.add()
.
// Add the GetFavoriteDogs functionapifunctions; // Apply the router afterapp;
.functions.add()
accepts a mapping of Functions, so you can do the following.
// Add multiple functionsapifunctions;
Using the API
Now that you have set up your Warp API API, you can start using it via either REST or the JavaScript SDK.
via REST
To learn more about the REST API, visit the REST documentation.
via JavaScript SDK
To learn more about the JavaScript SDK, visit the JavaScript SDK documentation.