@beardedtim/cms
WIP
Overview
This is very much a WIP. It currently is aiming to do two things:
- Simplify the startup of a Koa project
- Encourage building instead of setting up
I am hoping eventually to have this as a working Blogging/page-building platform. As of now, it is easy to add basic endpoints such as /resource
and /resource/:id
with little configuration.
Configuration API
{
collections: [
{
resource: 'collectionName',
pre: [ ... ], // async functions to run BEFORE route
post: [ ... ], // async functions to run AFTER route
protected: true, // if this is a protected route
authFn: validateFunc // custom async validation
route: KoaRoute // a KoaRouter compatable obj | defaults to Base(config) | no route pre/post added
}
],
pre: [ ... ], // async functions to run BEFORE ALL routes
post: [ ... ], // async functions to run AFTER ALL routes,
}
Developing
$ git clone git@github.com:beardedtim/cms.git
$ cd cms
$ cp .env.example
$ yarn
$ yarn dev
You will need a mongodb
instance running and have the correct MONGO_URI
set in your .env
file.
Working Examples:
Below you will find 4 examples of how to use the API. To run examples:
$ git clone git@github.com:beardedtim/cms.git
$ cd cms
$ yarn
$ cd examples
$ cd defaults
$ cp .env.example
$ node server.js
Data Flow
Each request to a valid endpoint will flow:
- Set expected values for
- All
config.pre
functions are applied in order - All
collections
are added as routes in order - All
config.post
functions are applied in order
Tutorial:
Inside of our root directory:
$ yarn add @beardedtim/cms
$ touch index.js
$ touch .env
Add the following to .env
:
PORT=5001
MONGO_URI=mongodb://localhost:32770/explore
AUTH_NAME=timi
AUTH_PASS=1234
Fill in the correct MONGO_URI
that you are using. If you are not already, checkout Docker and Kitematic for an almost point and click
setup experience.
Then inside of index.js
:
const dotenv = require('dotenv');
const Koa = require('koa');
dotenv.config();
const app = new Koa();
const cmsServer = require('./').server;
cmsServer.booststrap(app);
app.listen(5000);
We now have a functional applicaton at http://localhost:5001
with an endpoint at /documents
and /documents/:id
. It also has protected routes for PUT,PATCH,DELTE
methods.
Go ahead and try a GET
requests to http://localhost:5001/documents
. It should return the following:
{
data: []
}
Which means we have no documents. Let's fix that. Add the following to your request header and make a POST request:
Authorization: Basic dGltaToxMjM0
Content-Type: application/json
Body: {
name: 'Tim'
}
This can be done inside of postman
by clicking Authorization
, choosing Basic
and typing timi
for the name and 1234
for the password. It can also be added via fetch
:
fetch('http://localhost:5001/documents', {
method: 'POST',
headers: {
'Authorization': 'Basic dGltaToxMjM0',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Tim'
})
})
NOTE: If you get TypeError: Cannot create property '_id' on string
, it is probably because you did not set Content-Type
to application/json
You should get back the following:
{
"data": {
"insertedIds": [
"58fd730a37b1e45c818003b2"
]
}
}
If you get:
{
"error": {
"code": 406,
"message": "Unacceptable content-type! It must be applcation/json"
}
}
This is because you did not set the Content-Type
header correctly.
Now let's go back to GET /documents
and see what we get:
{
data: [
// ... whatever you pushed
]
}
And we should have an _id
. Go ahead and copy that and go to:
http://localhost:5001/documents/<ID HERE>
It should return:
{
"data": {
"_id": "58fd26666464e505d044556d",
"name": "Tim"
}
}
Or whatever you set that record to.
This is a boring record so let's delete it:
DELETE /documents/:id
DELETE /documents/58fd26666464e505d044556d
should return something like:
{
"data": {
"n": 1,
"ok": 1
}
}
And if we want to delete the whole collection of documents
and start again?
DELETE /documents
should return:
{
"data": true
}
And when we GET /documents
, we should be met with:
{
data: []
}
Adding Custom Functionality
Adding Custom 404
boostrapConfig = {
// ...
post: [
async (ctx, next) => {
if (!ctx.body) {
ctx.body = {
error: {
droids: 'not the ones you are looking for',
}
}
}
}
],
}
request: GET /not/real/route
response:
{
error: {
droids: 'not the ones you are looking for'
}
}
Adding Custom Query Validation
boostrapConfig = {
// ...
pre: [
async (ctx, next) => {
const { query } = ctx.request;
const isValid = customValidation(query);
if (!isValid) {
ctx.errors.custom({
code: 406,
message: 'Not valid query!',
});
}
}
]
}
request: GET /documents?invalid=true
response:
{
error: {
code: 406,
message: 'Not valid query!',
}
}