Crudl
CRUDL is a backend agnostic REST and GraphQL based admin interface
Install / Use
/learn @seeekr/CrudlREADME
crudl
CRUDL is a React application for rapidly building an admin interface based on your API. You just need to define the endpoints and a visual representation in order to get a full-blown UI for managing your data.
TOC
- About
- Architecture
- Options
- Admin
- Connectors
- Views
- List View
- Change View
- Add View
- Fieldsets
- Fields
- Permissions
- Messages
- Credits & Links
Architecture
The CRUDL architecture (depicted below) consists of three logical layers. The connectors, views, and the react-redux frontend. We use React and Redux for the frontend, which consists of different views such as list, add, and change view. The purpose of the connectors layer is to provide the views with a unified access to different APIs like REST or GraphQL. You configure the connectors, the fileds, and the views by providing a admin.
+-----------------------+
| React / Redux |
+-----------------------+
| Views |
+-----------------------+
↓ ↑ ↑ CRUDL
request response errors
↓ ↑ ↑
+-----------------------+
| Connectors |
+-----------------------+ ------------
↕
~~~~~~~
API BACKEND
~~~~~~~
Admin
The purpose of the admin is to provide CRUDL with the necessary information about the connectors and the views. The admin is an object with the following attributes and properties:
const admin = {
title, // Title of the CRUDL instance (a string or a react element property)
connectors, // an array of connectors
views, // a dictionary of views
auth: {
login, // Login view descriptor
logout, // Logout view descriptor
},
custom: {
dashboard, // The index page of the CRUDL instance (a string or a react element property)
pageNotFound, // The admin of the 404 page
menu, // The custom navigation
},
options: {
debug, // Include DevTools (default false)
basePath, // The basePath of the front end (default '/crudl/')
baseURL, // The baseURL of the API backend (default '/api/')
rootElementId, // Where to place the root react element (default 'crudl-root')
}
}
The provided admin will be validated (using Joi) and all its attributes and properties are checked against the admin's schema.
Attributes and properties
We distinguish between attributes and properties. An attribute is a value of a certain type (such as string, boolean, function, an object, etc.), whereas property can also be a function that returns such a value. In other words, with property you can also provide the getter method. For example, the title of the CRUDL instance is a string (or react element) property. So you can define it as
title: 'Welcome to CRUDL'`
or as
title: () => `Welcome to CRUDL. Today is ${getDayName()}
or even as:
title: () => <span>Welcome to <strong>CRUDL</strong>. Today is {getDayName()}</span>,
Options
In admin.options you may specify some general CURDL settings
{
debug: false, // Include DevTools?
basePath: '/crudl/', // The basePath of the front end
baseURL: '/api/', // The baseURL of the API (backend)
rootElementId: 'crudl-root', // Where to place the root react element
}
Assuming we deploy CRUDL on www.mydomain.com, we'll have CRUDL running on www.mydomain.com/crudl/... and the ajax requests of the connectors will be directed at www.mydomain.com/api/....
Connectors
The purpose of the connectors is to provide CRUDL with a unified view of the backend API. A connector is an object that defines the four CRUD methods create, read, update, and delete. These methods accept a request object as their argument and return a promise that either resolves to a response object or throws an error. Normally, a single connector represents a single API endpoint or a single resource. So you define, for example, a single connector to access the blog entries and another connector to access the users.
CRUDL provides connectors for RESTful and GraphQL APIs. A REST connector must define the url attribute and a GraphQL connector must define the query attribute.
A connector has the following schema:
{
id, // A string uniquely identifying the connector
url, // REST: The endpoint URL (will be appended to options.baseURL)
urlQuery, // REST: A function that builds the url query part
query, // GraphQL: The GraphQL queries for create, read, update, and delete operations
mapping, // The mapping between CRUD and HTTP methods
transform, // Definition of Request and Response transformations
pagination, // Function that returns pagination info
baseURL, // Overrides the value of admin.options.baseURL for this particular connector
}
-
url: url can either be a string such asusers/, that will resolve against thebaseURLoption. Or it can be a function of the form:(request) => urlString -
urlQuery: is an optional attribute. When provided, it must be a function(request) => query, wherequeryis an object of url query keys and values e.g.{ search: 'John', sortBy: 'last_name' }. The resulting URL would then be:baseURL/users?search=John&sortBy=last_name. -
query: An object with attributescreate,read,update, anddeleteeach defining a GraphQL query. The definition of the GraphQL query can be either a string or a function(request) => queryString -
mapping: An object that defines the mapping between the CRUD and HTTP methods. The default mapping of a REST connector is:{ create: 'post', read: 'get', update: 'patch', delete: 'delete', }The default mapping of a GraphQL admin is:
{ create: 'post', read: 'post', update: 'post', delete: 'post', } -
transform: An object of request and response transformations:{ // Request createRequest: (req) => req, readRequest: (req) => req, updateRequest: (req) => req, deleteRequest: (req) => req, // Request data createRequestData: (data) => data, readRequestData: (data) => data, updateRequestData: (data) => data, deleteRequestData: (data) => data, // Response createResponse: (res) => res, readResponse: (res) => res, updateResponse: (res) => res, deleteResponse: (res) => res, // Response data createResponseData: (data) => data, readResponseData: (data) => data, updateResponseData: (data) => data, deleteResponseData: (data) => data, }The transformation of a request is applied prior to the transformation of request data and similarly, the transformation of a response is applied prior to transformation of a response data.
-
pagination: a function(response) => paginationInfo, where the format ofpaginationInfodepends on the kind of pagination that is being used.The numbered pagination requires pagination info in the form:
{ allPages, currentPage, resultsTotal, filteredTotal }, whereallPagesis an array of page cursors. Page cursors can be any data.allPages[i-1]must provide a page cursor for the i-th page. ThecurrentPageis the page number of the currently displayed page. The corresponding page cursor of the current page isallPages[currentPage-1]. The total number of results can be optionally provided asresultsTotal. The total number of filtered results can be optionally provided asfilteredTotal.The continuous scroll pagination requires the pagination info in the form:
{ next, resultsTotal, resultsTotal, filteredTotal }. Where next is a pageCursor that must be truthy if there exist a next page, otherwise it must be falsy. TheresultsTotalis optional and it gives the number of the total available results. The total number of filtered results can be optionally provided asfilteredTotal. -
baseURL: A string that overrides theadmin.options.baseURLvalue for this particular connector. It allows to access different API at different base URLs.
Bare Connectors
If neither url nor query are provided, then the connector is called a bare connector and it must provide the CRUD methods directly, for example like this:
{
// Provide some testing data
read: () => Promise.resolve({
data: require('./testdata/tags.json')
}),
// Pretend to create a resource
create: (req) => Promise.resolve({
data: req.data
}),
},
Requests
A request object contains all the information necessary to execute one of the CRUD methods on a connector. It is an object with the following attributes:
{
data, // Context dependent: in a change view, the data contains the form values
params, // Connectors may require parameters to do their job, these are stored here
