Skip to main content

How to quickly create a REST CRUD API backend using codehooks.io - with code example

· 10 min read
jones
jones
Architect and Maker @ Codehooks

Create, read, update, and delete operations, often popularly known as CRUD — are essential in any application interacting with a database. In the schemaless and document oriented NoSQL world, it's important to control both query and data manipulation from your application. This blog post shows you how easy it is to create a full REST API with CRUD operations and validation for your application using codehooks.io.

socialcard

Setting up the CRUD API backend

To set up our CRUD / REST API backend we need to create a new Codehooks project. If you're new to Codehooks.io you can read more about getting started in the docs.

Use the CLI command coho create to create your project, e.g.:

coho create crud

cd crud

After creating your project you'll find your backend application network endpoint and a default API token by running the coho info command. In our REST API code example, the generated endpoint is https://crud-8mut.api.codehooks.io/dev/*. This is the endpoint that we´ll use in the code example in this blog post.

Great! Our backend endpoint is ready, now lets begin with the fun stuff. Let's create a complete REST API and data schema to interact with our NoSQL backend database.

Coding the CRUD API yourself?

A Codehook application is just JavaScript and it's up you as a developer to implement the best API for your backend application. In some cases you will create your own custom API and in other cases you need a standardized way to manage persistent data using a REST API. All codehooks.io backend applications (we call it spaces) has a backend database built right in and it is readily available from within the serverless functions.

For example, you can easily create a CRUD / REST API backend by adding the code hooks in your backend application like we show in the code snippet below, e.g. a CRUD API for a customer collection (implementation of the functions is left out as an exercise for the reader - just kidding 😁):

...
// API code example routes using codehooks.io
app.post('/customer', createCustomerFunc);
app.get('/customer', readManyCustomerFunc);
app.get('/customer/:ID', readOneCustomerFunc);
app.put('/customer/:ID', updateCustomerFunc);
app.patch('/customer/:ID', updateCustomerFunc);
app.delete('/customer/:ID', deleteCustomerFunc);
...

However, when you just need a basic CRUD API, coding of this is a manual and repetitive task that doesn't add much real value to your application.

That is why we have created the Crudlify library. Crudlify integrates with your codehooks.io database backend and generates a full fledged CRUD REST API backend using one of the popular schema validation libraries Yup and JSON schema.

Let Crudlify do your CRUD work

Crudlify automates and dynamically creates a CRUD API with schema validation for any codehooks.io backend application. For client database queries we use the package query-to-mongo which converts URL query parameters into a MongoDB-like query criteria and options. Crudlify also support regular mongoDB queries for more advances use cases.

Let's look at a minimal CRUD REST API backend with one collection and without any schema or logic.

A minimal CRUD API code example

Our minimal codehooks.io JavaScript serverless REST CRUD API backend function is shown below. Before deploying the code we also need to add all dependencies, i.e. yup, codehooks:

npm i yup codehooks-js

Save the code below in your favorite code editor, and you are ready to deploy.

index.js
/*
* Minimal CRUD API code example using Yup schema
*/
import {app, crudlify} from 'codehooks-js'
import * as yup from 'yup';

// "product" schema allowing any json object
const product = yup.object().shape({
json: yup.mixed()
})

// Add CRUD routes with yup schema for two collections
crudlify(app, {product})

// bind to serverless runtime
export default app.init()

To deploy our CRUD API backend we use the command:

coho deploy

This effectively deploys the backend application to the cloud, providing a full-fledged REST API backend with POST GET PUT PATCH DELETE operations, for one collection - product.

That's just 6 lines of JavaScript code giving you a CRUD REST API backend ready to use 😎

Let's test it!

Testing our CRUD REST API backend with Postman

Postman is a great way to test your REST APIs. Before we can test our CRUD REST API backend we need to add a secure API token as a header value x-apikey: XXXX. Create a new token listed from the coho add-token or pick from an existing token with the coho info command.

Lets try to POST to the product collection using our backend application REST API:

https://crud-8mut.api.codehooks.io/dev/product

The screenshot below show that we can successfully add a product to our backend database.

postman

The equivalent curl REST API call example is shown below:

curl --location --request POST 'https://crud-8mut.api.codehooks.io/dev/product' \
--header 'x-apikey: XXXXXXXXX' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "milk",
"price": 1.2,
"stock": 14
}'

Refining and expanding the CRUD REST API backend using the Yup schemas

To add multiple collections to your application you only need to add them to the schema definitions, it's that simple. For example, and perhaps a bit more realistic, adding a customer collection with a data schema is shown in the example below:

...
// customer schema
let customer =
yup.object().shape({
name: yup.string().required(),
status: yup.mixed().oneOf(['open', 'suspended', 'closed']).required(),
balance: yup.number().required(),
products: yup.array().of(
yup.object({
name: yup.string().required(),
price: yup.number().required()
})
)
})

// product schema, any json is allowed
let product = yup.object().shape({
json: yup.mixed()
})

// Add CRUD routes with yup schema for two collections
crudlify(app, {product, customer})
...

Now, with a proper data schema, if we try to POST an invalid object to our application, schema validation by Yup will stop us and give an error message, e.g. POST'ing this object to our API:

{
"name": "Jill James"
}

Will return a status code 400 with this error message/object:

{
"value": {
"name": "Jill James"
},
"path": "balance",
"type": "required",
"errors": [
"balance is a required field"
],
"params": {
"path": "balance"
},
"inner": [],
"name": "ValidationError",
"message": "balance is a required field"
}

Using Crudlify, our codehooks.io backend will automatically have the following endpoints for any collection defined in your data schema.

VerbCodehooks.io routeDescription
GEThttps://<projectid>.api.codehooks.io/:space/:collection[?field=value&...]Retrieve all objects or use API query
GEThttps://<projectid>.api.codehooks.io/:space/:collection/:IDRetrieve object by ID
POSThttps://<projectid>.api.codehooks.io/:space/:collectionAdd object
PUThttps://<projectid>.api.codehooks.io/:space/:collection/:IDReplace object by ID
PATCHhttps://<projectid>.api.codehooks.io/:space/:collection/:IDUpdate object by ID
PATCHhttps://<projectid>.api.codehooks.io/:space/:collection/_byqueryUpdate object(s) by query
DELETEhttps://<projectid>.api.codehooks.io/:space/:collection/:IDDelete object by ID
DELETEhttps://<projectid>.api.codehooks.io/:space/:collection/_byqueryDelete object(s) by query

Replace <projectid> with the project name and insert space name into :space and collection into :collection, for example:

https://crud-8mut.api.codehooks.io/dev/product

Where projectid is crud-8mut, space is dev and collection is product.

Use your favorite schema provider 🔥

codehooks-js/crudlify supports these popular data and validation schemas:

  • Yup - Dead simple Object schema validation
  • Zod - TypeScript-first schema validation with static type inference
  • JSON.schema - Standard declarative language that allows you to annotate and validate JSON documents

Query the database with mongoDB-like queries

The codehooks.io database uses a subset of the mongoDB query language. This is perfectly adapted to JSON and well suited for backend programming in JavaScript.

However, when exposing a REST API, complex JSON objects can be somewhat tricky having only the URL query string as the main channel between the client app and your backend.

To make client development easy, the Crudlify package uses the query-to-mongo lib. This implements a URL based query language that follows the web standard closely. For example, we can query our database for all customers with status = open and balance > 0 like this:

https://crud-8mut.api.codehooks.io/dev/customer?status=open&balance>0

For advanced use, and programmatic approach, you can pass inn the full JSON query and hints as URL objects:

https://crud-8mut.api.codehooks.io/customer?q={"status": "open", "balance": {"$gt": 0}}&h={"fields": { "name": 1, "balance": 1 },"sort": {"balance": 1 },"skip": 5,"limit": 2}

The last option would probably use JSON.stringify(query) etc. to produce a valid query in the URL.

Overriding stuff with middleware

In codehooks.io you can use middleware to override functionality on top of any application, including the example CRUD app we've made here. Lets say we want to add a function that calculates the customer balance based on the sum of the product records in an API operation. The API code example below shows how a global middleware could intercept the HTTP method and the collection to add this logic:

...
// global middleware on Customer route
app.use('/customer', (req, res, next) => {
if (req.method === 'POST') {
if (req.body.products) {
req.body.balance = req.body.products.reduce((accumulator, object) => {
return accumulator + object.price;
}, 0);
}
}
next()
})
...

When the client makes a POST like the example below:

{
"name": "Bill Roads",
"status": "open",
"products": [
{"name": "milk", "price": 1.2},
{"name": "juice", "price": 3}
]
}

The middleware automatically mutates the request body before is's added to the database, like shown in the example result below:

{
"name": "Bill Roads",
"status": "open",
"products": [
{
"name": "milk",
"price": 1.2
},
{
"name": "juice",
"price": 3
}
],
"balance": 4.2,
"_id": "1849588d162-n0upyc0ec8y1po"
}
New event hooks middleware

In the newest release Crudlify incorporates a much more powerful feature for adding additional logic to your application. Add your custom logic for any database operation.

Event hooks format
hooks.before<VERB>(<COLLECTION>, handlerFunction)
hooks.after<VERB>(<COLLECTION>, handlerFunction)

For example:

crudlify(app, {product, customer}).then((hooks) => {
hooks.beforePOST('customer', async (data) => {
// do something here
data.foo = 'Was here before saving!'
})
hooks.afterPOST('product', async (data) => {
console.log("Data as saved to the database", data)
// do something here
})

What about authentication?

In many cases, it would be very useful for a CRUD REST API to have some form of user authentication. API keys are not recommended if you create a web client using the CRUD backend API.

You can find more about how to set up authentication here:

Summary and takeaways

We've shown you how to quickly set up a complete CRUD REST API backend with a 6-lines long API code example showing the power of the codehooks.js/crudlify library in combination with the simple deployment of codehooks.io.

We also recommend taking a closer look at the excellent Yup schema builder. There's a lot more you can do with it than we have shown here.

Key takeaways:

  • Yup schema is easy to learn
  • Automatic creation of a CRUD REST API with Crudlify and codehooks.io is super simple (just copy the API code example to test it out)
  • If you prefer plain JSON and JSONSchema to the fluent code-based approach of Yup, the codehooks.js/crudlify. supports this too.

Look out for Swagger / OpenAPI support in the near future!

Github

The full source code for this example is avaliable here.