# Codehooks Documentation for LLMs
> This is the Codehooks documentation for LLMs. Use this to get an overview of the Codehooks platform and how to use it. Use the 'llms-full.txt' file for detailed answers.
This file contains all documentation content in a single document following the llmtxt.org standard.
## Overview
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
import myImageUrl from './images/quickstart/collection-with-data.png';
import CodeBlock from '@theme/CodeBlock'
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
import Link from '@docusaurus/Link';
import Badge from '@mui/material/Badge';
import {Button, Grid} from '@mui/material';
import OverviewPng from './images/Overview-1.png';
import Studio1Png from './images/studio/studio1.png';
import Studio2Png from './images/studio/studio2.png';
import Studio3Png from './images/studio/studio3.png';
import Studio4Png from './images/studio/json-schema.png';
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
import FolderOpenIcon from '@mui/icons-material/FolderOpen';
import LayersIcon from '@mui/icons-material/Layers';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import {
CogIcon,
CheckCircleIcon,
CheckIcon,
DocumentCheckIcon,
RocketLaunchIcon,
CloudArrowUpIcon,
MagnifyingGlassPlusIcon,
LockClosedIcon,
PlayIcon,
CircleStackIcon,
DocumentPlusIcon,
KeyIcon,
ClockIcon,
RectangleStackIcon,
ChartBarSquareIcon,
LinkIcon,
QueueListIcon,
SparklesIcon
} from '@heroicons/react/24/outline';
export const NumberIcon = ({ index = -1, style = {}}) => (
{index}
);
export const codex0 = `import {app} from 'codehooks-js'
// Use Crudlify to create a REST API for any database collection
app.crudlify()
// bind to serverless runtime
export default app.init();
`;
export const codex1 = `const URL = 'https://myproject-fafb.api.codehooks.io/dev/orders';
response = await fetch(URL+'?status=OPEN', /* options */)
const result = await response.json()
`;
export const codex2 = `import {app, datastore} from 'codehooks-js'
// REST API route
app.get('/sales', async (req, res) => {
// highlight-next-line
const conn = await datastore.open();
const query = { status: "OPEN" };
const options = { sort: { date: 1 } }
conn.getMany('orders', query, options).json(res); // stream JSON to client
})
// bind to serverless runtime
export default app.init();
`;
export const codex3 = `import {app, datastore} from 'codehooks-js'
// REST API route
app.post('/cacheit', async (req, res) => {
const conn = await datastore.open();
const opt = {ttl: 60*1000};
// highlight-next-line
await conn.set('my_cache_key', '1 min value', opt);
res.send('Look dad, I have my own cache now!');
})
// bind to serverless runtime
export default app.init();
`;
export const codex4 = `import {app} from 'codehooks-js'
// serve the assets directory of the deployed project source code
// highlight-next-line
app.static({directory: "/assets"})
// serve a direcory of Blob storage files
// highlight-next-line
app.storage({route:"/documents", directory: "/myblobs"})
// CRUD REST API
crudlify(app)
// bind to serverless runtime
export default app.init();`;
export const codex5 = `import app from 'codehooks-js'
// Trigger a function with a CRON expression
// highlight-next-line
app.job('*/5 * * * *', (_, job) => {
console.log('Hello every 5 minutes')
// Do stuff ...
job.end()
});
export default app.init();`;
export const codex6 = `import { app, Datastore } from 'codehooks-js'\n
// register a Queue task worker
// highlight-next-line
app.worker('ticketToPDF', (req, res) => {
const { email, ticketid } = req.body.payload;
//TODO: implement code to fetch ticket data, produce PDF
// and send email with attachment using Mailgun, Sendgrid or Amazon SES
// ...and implement error handling
res.end(); // done processing queue item
})\n
// REST API
app.post('/createticket', async (req, res) => {
const { email, ticketid } = req.body;
const conn = await Datastore.open();
// highlight-next-line
await conn.enqueue("ticketToPDF", { email, ticketid });
res.status(201).json({"message": \`Check email $\{email\}\`});
})\n
export default app.init();
`;
export const codex7 = `import { app, Datastore, aggregation } from 'codehooks-js'
... // collapsed code
const spec = {
$group: {
$field: "month",
$max: ["sales", "winback"],
$sum: "sales"
}
}
const db = await datastore.open();
const dbstream = db.getMany('salesData');
// highlight-next-line
const result = await aggregation(dbstream, spec)
... // collapsed code
`;
export const codex8 = `{
"oct": {
"sales": {
"max": 1477.39,
"sum": 1234.05
},
"winback": {
"max": 22.0
}
},
"nov": {
"sales": {
"max": 2357.00,
"sum": 5432.00
},
"winback": {
"max": 91.0
}
},
}`;
export const SmallFeature = ({
icon,
link = '#',
title = 'No title',
content =
);
};
## Codehooks: Integrate and Automate everything
Codehooks provides powerful tools for developers to build, connect, and process APIs with ease. Create projects using our intuitive command line interface or web-based admin UI. A Codehooks.io project contains multiple isolated spaces - complete self-contained environments with their own database, settings, and code.
The **[`codehooks-js`](https://www.npmjs.com/package/codehooks-js)** library is the foundation for all Codehooks JavaScript development, offering a comprehensive set of 7 specialized APIs that accelerate development, streamline data integration, and simplify data processing. Explore each capability below to see how Codehooks can improve your development experience.
:::tip API Cheat Sheet
Check out the [API Cheat Sheet](/docs/apicheatsheet) for a quick overview of all the APIs
:::
If you're like most developers (i.e. smart and lazy π), we've added a [ChatGPT and LLM Prompt](/docs/chatgpt-backend-api-prompt) you can use to kickstart new projects. The prompt itself is worth looking at to get an overview of the features of the codehooks-js library. Our new MCP Server implementation has this prompt built-in.
:::tip Codehooks CLI tool
To deploy code (and much more), you use the Codehooks CLI tool:
`npm install codehooks -g`
Code editing and deployment will also soon be available in the web based UI.
:::
## API development
}
title="Get a flying start with an automatic CRUD REST API"
content={
Consider a Codehooks project, for example myproject-fafb.
Under the hood, Codehooks deploys an automatic REST API which is made up
of the following really short - but complete application code:
{codex0}
It's just 3 lines of code π₯, and it delivers a fully secure CRUD REST
API with a NoSQL database for JSON queries and updates. Client apps can
instantly connect to your data API, for example from a JavaScript app
like the example code snippet shown below:
{codex1}
}
nextsteps={[
Database REST API detailed docs,
Data validation and schema support
,
Develop custom REST APIs,
Client code examples,
Cheat Sheet for all the APIs,
]}
/>
## NoSQL and Key-Value Datastores
}
title="Easy database API inspired by MongoDB"
content={
Sometimes a standard CRUD REST API isn't enough. With custom JavaScript
functions you can create logic that integrates directly to the NoSQL
datastore. No need to think about drivers, versions, protocol or
security, the database is just there, available as an intrinsic object
in every serverless function.
{codex2}
}
nextsteps={[
Getting started,
Database API,
NoSQL query language
,
Database indexing,
Check output logs with the CLI,
Query data with the CLI,
]}
/>
}
title="Speed up your application with a Key-Value cache"
content={
The Codehooks data engine includes a simple key-value datastore. A
key-value datastore has many use-cases, and is ideal for caching due to
the swift data access and simple, maintenance-friendly architecture. A
simple example is shown below, where a string is stored for 60 seconds
with the ttl option.
{codex3}
}
nextsteps={[
Key-Value API,
Tutorials,
]}
/>
## Background Jobs
}
title="CRON expressions + your function keeps running"
content={
Use simple CRON expressions to automate and manage the execution of
routine tasks. You can schedule a job to run at multiple, disparate
times. For example, you can schedule a task to run at 2 AM on Sundays
and 4 PM on Wednesdays with the same CRON job.
In the simple example below, the CRON expression{' '}
*/5 * * * * will trigger a JavaScript function each 5
minutes.
}
title="Scale workloads with queues and worker functions"
content={
Use persistent queues and "worker" functions to create robust,
efficient, and highly responsive back-end systems. Queues scales
processing across multiple nodes and will restore and continue working
in case of errors and system failures.
Check out the example below, where a REST API request starts a complex
task by adding it to the Queue for later processing.
Create and deploy robust, scalable workflows using queued functions and state management.
Build reliable backend systems with automatic retry, state persistence, and distributed processing.
The example below shows a simple workflow that processes customer onboarding with email validation.
{`import { app } from 'codehooks-js';
const workflow = app.createWorkflow('customerOnboarding', 'Customer signup and email validation', {
START: async function (state, goto) {
state = { email: state.email, validated: false };
await sendWelcomeEmail(state.email);
goto('waitForValidation', state);
},
waitForValidation: async function (state, goto) {
if (state.validated) {
goto('complete', state);
} else {
wait({wait_message: 'Waiting for subflow to complete'});
}
},
complete: function (state, goto) {
state.status = 'completed';
goto(null, state); // workflow complete
}
});
// Start workflow from REST API
app.post('/start-onboarding', async (req, res) => {
const result = await workflow.start({ email: req.body.email });
res.send(result);
});`}
}
nextsteps={[
Workflow API,
Example Workflows
]}
/>
## File System & Blob storage
}
title="Serve any document or file content"
content={
Codehooks has a built-in file system to support streaming file uploads
and downloads. You can serve files via a static fileserver or via
JavaScript functions using the File API. The example CLI command below
shows how to upload a directory of files to the Blob storage{' '}
dev space (environment) of the project. You can publish
documents and files in two ways. Use the file-upload CLI command for
mass uploads of files/directories:
Or deploy file content located as part of your server code with the
deploy command:
$ coho deploy
The example below shows how to serve both deployed files and Blob
storage content.
{codex4}
}
nextsteps={[
File API,
Upload files with the CLI,
]}
/>
## Data Aggregation
}
title="Easy to use data aggregation format"
content={
Transform your live data streams into insights with just a few lines of
code. Codehooks has a simple but powerful JSON-based aggregation
expression language to specify data transformations. Groups and sums can
be nested to create both simple and advanced data outputs. The
aggregation API can process input from any JSON data-stream, for example
the query output from the NoSQL datastore. π₯
{codex7}
The example aggreagation transformation produces JSON output similar to
the example below.
{codex8}
}
nextsteps={[Aggregation API]}
/>
## Next Steps
Read this far? Great π
It's time to get your feet wet π¦. Why don't you start thinking about how codehooks' toolbox can help you build your solution. You can get started with the [quickstart for developers](/docs/quickstart-cli) or just [sign up](https://account.codehooks.io) and create your project and space using the web UI. You can easily upload some CSV data and immediately test your new API.
The documentation or tutorials are great places to start. If you need more help, feel free to contact us on the chat.
---
## Concepts overview
*(Note: This content contains MDX/JSX code)*
Codehooks.io simplifies and speed up system integration and API development by combining serverless JavaScript functions and fast Database persistence with a coherent and well documented API. Applications are easily deployed to the secure and scalable Codehook managed cloud.
Forget about cluttering servers, databases, infrastrucure and protocols - just focus on what matters.
Codehooks includes these main features:
- **Studio** - Developer friendly web based tools for managing both data and code
- **Node.js and JavaScript** - Same language for frontend and backend development (ES6 and Typescript)
- **NPM** - Node.js package manager (npm) integration
- **Node.js Express** - API development with Express-like API
- **NoSQL** - Document database with Mongodb-like API
- **Key-Value** - Key-Value database with a Redis-like API (subset)
- **Worker Queues** - Persistent queues with worker functions
- **Background jobs** - Cron jobs with scheduled worker functions
- **CLI** - Powerful Command Line Interface (CLI) for productivity and DevOps/CI support
Read on to learn more about how development with Codehooks.io can simplify and add value to your project and team.
> Unless you've already have, please read the [quick start](/docs) first to learn how to install the CLI, and how to sign up/login to create your first Codehooks project.
## Codehooks main concepts
### Project
A project is the top level concept in Codehooks. A project is owned by an account (private or company) and contains one or multiple "spaces" with your deployed code, data and access credentials in an isolated and secure unit. You can create multiple projects within your account and you can join/invite others to a project. An example project structure is shown below:
- jane@example.com (private or corporate account owner)
- **customers** (project name)
- **dev** (development space)
- Database instance
- Security settings (API tokens, environment secrets, IP filters ...)
- Source code (deployed version 1.1.12)
- Developers (joe@example.com, pete@example.com)
- **prod** (production space)
- Database instance
- Security settings (API tokens, environment secrets, IP filters ...)
- Source code (deployed version 1.1.0)
- Developers (joe@example.com, pete@example.com)
- **salesdb** (project name for another project)
- ...
Projects are created with the CLI command `coho create` ([CLI docs here](cli#create))
:::info note
You can also manage your projects and spaces using the account user interface at [https://account.codehooks.io](https://account.codehooks.io).
:::
### The project application source code
A Codehooks application follows the familiar principles and best practices of modern JavaScript development. A project directory typically contains:
- An `index.js` file for the application main entry code
- A `package.json` file with library dependencies
- A `config.json` file with project information
We also recommend that you add source control (GIT) to manage your versions and branches.
Typically this will be a git repo with branches that are deployed to various spaces.
### A minimal example
The following example shows a complete process for creating a new project with an application that is deployed to the Codehooks serverless cloud.
First, create and setup a new project application:
```bash
coho create example
cd example
npm init --yes
npm install codehooks-js --save
```
After running the commands your project directory contains these files:
```bash
.
βββ config.json
βββ index.js
βββ node_modules
βββ package-lock.json
βββ package.json
```
If you inspect the source file index.js you'll see the default generated application code:
```js
/*
* Auto generated Codehooks (c) example
*/
import { app, crudlify } from 'codehooks-js';
// test route for https://.api.codehooks.io/dev/
app.get('/', (req, res) => {
res.send('CRUD server ready');
});
// Use Crudlify to create a REST API for any collection
crudlify(app);
// bind to serverless runtime
export default app.init();
```
We can now deploy the application (in our case example-4g9p) to the cloud with the CLI command `coho deploy` ([CLI docs here](cli#deploy)).
```bash
coho deploy
# Server output example
Project: example-4g9p Space: dev
Deployed Codehook successfully! π
```
After deployment we can inspect the application details with the `coho info` command, in this example showing our default space `dev` with its base endpoint URL and access token(s):
```bash
coho info --examples
Project name: example-4g9p
Team: YOUR-NAME (personal account)
API endpoint: https://example-4g9p.api.codehooks.io/dev/*
Spaces:
ββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ¬βββββββ¬βββββββ
β Name β Tokens β Jwks β Env β
ββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌβββββββΌβββββββ€
β dev (active) β a77926ca-xxx-yyyy-zzzzz-14eee564f8d5 (RW) β β β
ββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββ΄βββββββ΄βββββββ
```
After successful deployment we can test our application endpoint with curl. This minimal example and test shows that our application is deployed to a secure endpoint and returns the expected result:
```bash
curl -X GET 'https://example-4g9p.api.codehooks.io/dev' \
-H 'x-apikey: a77926ca-xxx-yyyy-zzzzz-14eee564f8d5'
# API output example from server
CRUD server ready
```
### Project spaces
A project space is a self-contained and isolated bundle of code, datastores and security settings within a specific project. All projects have a default space called `dev` which is created automatically when you add a project. You create new spaces with the `coho add` command ([CLI docs here](cli#add)). You can easily switch between different project spaces with the `coho use` command ([CLI docs here](cli#use)). Spaces can also have restricted access (team ADMINs only), which is convenient for production deployments.
For example, in your project you want to have isolated development, testing and production spaces.
```
Project-X
βββ dev
Β Β βββ source code (git branch dev)
Β Β βββ security settings & env variables
Β Β βββ data stores
βββ test
Β Β βββ source code (git branch test)
Β Β βββ security settings & env variables
Β Β βββ data stores
βββ prod (restricted)
Β Β βββ source code (git branch prod)
Β Β βββ security settings & env variables
Β Β βββ data stores
```
### Deploy your application to the Codehooks serverless cloud
Each project space is created as an isolated resource group in the Codehooks cloud.
For example, the `coho add prod` command creates a project space called `prod`. By switching to the space with the `coho use prod` command, this space is now the active (changed in the config.json project file).
On deployment with the `coho deploy` command, the current version of your application source code is packed and distributed as a serverless runtime to the **prod** space in the Codehook cloud.
Similarily, switching to another space, e.g. by using the `coho use dev`, sets the **dev** space as the active, and `coho deploy` will deploy the application to the active **dev** space.
This is a powerful feature, which effectively enables you to run multiple versions of your application with separate settings, environments and data stores.
### Built in Datastores
Persisting, querying and manipulating data is essential in any application. Codehooks comes with a built in datastore that supports **streaming** NoSQL and Key-Value operations. No need to think about drivers and protocols, it's built directly into the Codehooks APIs.
- NoSQL object data are accessed with the standard [Database API](nosql-database-api).
- Key-Value data are accessed with the standard [Key-Value API](key-value-database-api)
#### Data wrangling
Using the CLI command `coho import`, you can easily import any CSV or JSON file into a datastore.
Furthermore, the `coho query` command lets you query and transform data in advanced ways.
```bash title="Example: import and query of Stock data"
coho import -c stocks -f ~/Downloads/all_stocks_5yr.csv
coho query stocks --query 'Name=GOOG&open>10' --limit 2 --table
βββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ
β date β open β high β low β close β volume β Name β _id β
βββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββ€
β 2013-03-β¦ β 15.98 β 16.36 β 15.93 β 16.25 β 8383300 β GOOG β 18007b5fβ¦ β
βββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββ€
β 2013-03-β¦ β 14.7 β 14.93 β 14.5 β 14.82 β 9125300 β GOOG β 18007b5fβ¦ β
βββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ
```
### Fast development flow
The development process with Codehooks is fast.
You use your favorite tools (e.g. VS Code) combined with the CLI.
For example, after creating a new project and a project space, you write some code that you need to verify, test, debug and deploy. The following steps shows a typical development flow.
1. Open the index.js and other source files in your favorite code editor
2. Set the active space to deploy your application, e.g. `coho use dev`
3. Deploy the application with `coho deploy`
4. Check logs to see what's going on in your deployed application, `coho log --follow`
5. Find and fix bugs in your code, and redeploy with `coho deploy`
### Command Line Interface (CLI)
The Codehooks CLI is a key tool for managing your account, projects and applications. The CLI has all the commands and functionality you need to manage projects, spaces, security and your serverless JavaScript functions.
Codehooks has a complete set of features for teams to collaborate.
The [account UI](https://account.codehooks.io) lets you create teams and invite team members.
Read more about the CLI and the full set of commands and features [here](cli).
:::info note
You can also manage your projects and spaces using the account user interface at [https://account.codehooks.io](https://account.codehooks.io).
:::
---
## Getting started
*(Note: This content contains MDX/JSX code)*
This short tutorial will show you how to create codehooks projects and spaces using the command line interface (CLI). Check out the [concepts](concepts) page for a more in-depth introduction. To use the Studio to create projects, check out this [quickstart](/docs).
## Install the CLI
Install the Codehooks command line interface (CLI), this lets you fully manage your projects from the command line.
```bash
npm i -g codehooks
```
> You need [Node.js](https://nodejs.org) to install the CLI.
:::tip Use 'Dev containers' to set up your codehooks development environment
Our CLI requires use of the terminal window, which can sometimes be challenging especially for Windows users. Check out our blog post about [how to set up a dev container](/blog/simplify-codehooks-development-with-devcontainers) for simplified and more consistent codehooks.io development environment on all platforms.
:::
## Sign up and Log in
Next you need to sign up / log in to your account (it's totally free), in this example we use a Github account. Your browser will open and direct you a secure login page, and there you can return to the CLI - logged in.
```bash
coho login
```
Then choose login method, e.g. Github.
```bash
Select option below
β― Use Github
Use Google
Exit
```
## Create project
Lets go forward and create a new project on your account.
A project contains the source code for your serverless functions.
```bash
coho create myproject
```
Follow the guide to create a personal or team project type.
Finally change directory to the new project and install the Codehooks standard library.
```bash
cd myproject
```
The `coho create` command has now created a new project space and generated a unique name for the API, in this example it's named `myproject-2b39` and `dev`.
Your account will be the owner of the project. This can be changed later.
:::note Deploy code to an existing project
If you have an existing project you'd like to use, for example created with the Studio application, you can connect and deploy the code to that project using the [CLI command](/docs/cli#init) `coho init` instead of `coho create`.
:::
## Create a serverless JavaScript Codehook
First, in the project directory, install the Codehooks standard open source libraries [codehooks-js](https://www.npmjs.com/package/codehooks-js).
```bash
npm i codehooks-js
```
Next, start your favorite code editor and open the auto generated `index.js` in your project directory.
```js title="index.js"
/*
* Auto generated Codehooks (c) example
*/
import { app } from 'codehooks-js';
// test route for https://.api.codehooks.io/dev/
app.get('/', (req, res) => {
res.send('CRUD server ready');
});
// Use Crudlify to create a REST API for any database collection
app.crudlify();
// bind to serverless runtime
export default app.init();
```
Save the JavaScript file.
## TypeScript support
Codehooks supports TypeScript (version 5) with strong typing. Just rename the index.js file to index.ts, update the package.json main property, and your're ready to go.
The code example below shows the initial code example using TypeScript.
```ts title="index.ts"
/* TypeScript */
import { app, httpRequest, httpResponse } from 'codehooks-js';
// strong typing for request and response
app.get('/', (req: httpRequest, res: httpResponse) => {
res.send('CRUD server ready');
});
// Use Crudlify to create a REST API for any database collection
app.crudlify();
// bind to serverless runtime
export default app.init();
```
```js title="package.json"
{
// rest of your package.json file
"main": "index.ts"
}
```
You are now ready to deploy your project.
## Deploy the code to the serverless cloud
```bash
coho deploy
```
Example output from the deploy command.
```bash
Deploying to Project: myproject-2b39 Space: dev
Deployed Codehook successfully! π
```
You can now test the [database CRUD REST API](/docs/databaserestapi.md) with a sample collection, for example `users`.
:::tip
Use the `coho info --examples` command to find your project name, API tokens and working curl examples.
:::
```bash title="curl shell command - POST a new user"
curl --location 'https://.api.codehooks.io/dev/users' \
--header 'x-apikey: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Bill",
"email": "william@example.com",
"active": true
}'
```
Example output from the curl test.
```js
{
"name": "Bill",
"email": "william@example.com",
"active": true,
"_id": "6421b3e6a3c762051688edf7"
}
```
Lets also test a query for the same data we've added.
```bash title="curl shell command - query users"
curl --location 'https://.api.codehooks.io/dev/users?name=Bill' \
--header 'x-apikey: ' \
--header 'Content-Type: application/json' \
```
Which returns an array of 1 object from the database.
```js
[
{
name: 'Bill',
email: 'william@example.com',
active: true,
_id: '6421b3e6a3c762051688edf7',
},
];
```
Our test shows that the automatic REST API is accessible on the `/dev/users` route, and we can successfully add and retrieve data from the database to the client.
:clap::clap:
---
## Data aggregation API
*(Note: This content contains MDX/JSX code)*
The JSON aggregation specification is designed to provide a structured way to aggregate JSON data streams. It offers several operators to aid in the summarization and grouping of data streams without the need for extensive code.
**Aggregation Operators**
Our specification currently supports the following operators:
- `$max`: Determines the maximum value for the specified field(s).
- `$min`: Determines the minimum value for the specified field(s).
- `$avg`: Computes the average value for the specified field(s).
- `$sum`: Sums up the values for the specified field(s).
- `$group`: Group by specified field and perform nested aggregations.
Each operator can take either a string denoting the field name or an object with additional properties such as $field (for specifying the field name) and $label (for specifying a custom label for the result).
## Aggregation data from a NoSQL query
JSON data `aggregation` is a standard library that is imported in your Codehooks app.
The example below shows how to use the library agains a `salesData` NoSQL data collection.
```js {5-8,12}
import { app, datastore, aggregation } from 'codehooks-js';
app.post('/agg', async (req, res) => {
try {
const spec = {
$max: 'sales',
$avg: 'sales',
};
const db = await datastore.open();
const dbstream = db.getMany('salesData', { active: true });
const result = await aggregation(dbstream, spec);
console.log(result);
res.json(result);
} catch (error) {
console.log(error.message);
res.status(400).end(error);
}
});
export default app.init(); // Bind functions to the serverless runtime
```
**Example output**
```json
{
"count": 120500,
"sales": {
"max": 1234.0,
"avg": 123.0,
"sum_total": 123456.0
}
}
```
## Operators
### `$max`
- **Description:** Determines the maximum value for the specified field(s).
- **Usage:**
- `$max: fieldName` for a simple field.
- `$max: { $field: fieldName, $label: customLabel }` for specifying a custom label.
- **Example:**
```json
{ "$max": { "$field": "sales", "$label": "Max Sales" } }
```
### `$min`
- **Description:** Determines the minimum value for the specified field(s).
- **Usage:**
- `$min: fieldName` for a simple field.
- `$min: { $field: fieldName, $label: customLabel }` for specifying a custom label.
- **Example:**
```json
{ "$min": { "$field": "sales", "$label": "Min Sales" } }
```
### `$avg`
- **Description:** Computes the average value for the specified field(s).
- **Usage:**
- `$avg: fieldName` for a simple field.
- `$avg: { $field: fieldName, $label: customLabel }` for specifying a custom label.
- **Example:**
```json
{ "$avg": { "$field": "sales", "$label": "Average Sales" } }
```
### `$sum`
- **Description:** Sums up the values for the specified field(s).
- **Usage:**
- `$sum: fieldName` for a simple field.
- `$sum: { $field: fieldName, $label: customLabel }` for specifying a custom label.
- **Example:**
```json
{ "$sum": { "$field": "sales", "$label": "Total Sales" } }
```
### `$group`
- **Description:** Group by specified field and perform nested aggregations.
- **Usage:**
- `$group: { $field: fieldName, ...nestedAggregations }` to group by a field and apply further aggregations.
- **Example:**
```json
{
"$group": {
"$field": "customerType",
"$label": "Customer Segment",
"$max": "sales"
}
}
```
#### Example: Complex grouping and aggregation
```js
const spec = {
$max: { $field: 'sales', $label: 'All time top sales' },
$group: [
{
$field: 'customerType',
$label: 'Segment',
$max: 'sales',
$min: { $field: 'sales', $label: 'Low' },
$group: {
$field: 'year',
$group: {
$field: 'month',
$max: ['sales', { $field: 'volume', $label: 'Sales volume' }],
},
},
},
{
$field: 'day',
$max: 'sales',
},
],
};
```
Our JSON aggregation specification provides a robust tool for developers seeking powerful and streamlined data processing capabilities. Harnessing these operators allows for comprehensive insights into your data streams.
---
## Codehooks.io API Cheat Sheet
*(Note: This content contains MDX/JSX code)*
**Everything you need to build a serverless backend in one place.** This cheat sheet contains all essential APIs for creating complete serverless applications with Codehooks.io. Quick reference for routing, authentication, NoSQL databases, workflows, queues, key-value stores, and real-time features. Each API includes direct links to detailed documentation with examples and usage patterns.
## **HTTP Routing APIs**
- **`post(path, function)`** - Register POST route handlers β [Details](/docs/appeventapi#apppostroute-workerfunction)
- **`get(path, function)`** - Register GET route handlers β [Details](/docs/appeventapi#appgetroute-workerfunction)
- **`put(path, function)`** - Register PUT route handlers β [Details](/docs/appeventapi#appputroute-workerfunction)
- **`patch(path, function)`** - Register PATCH route handlers β [Details](/docs/appeventapi#apppatchroute-workerfunction)
- **`delete(path, function)`** - Register DELETE route handlers β [Details](/docs/appeventapi#appdeleteroute-workerfunction)
- **`all(path, function)`** - Register handlers for all HTTP methods β [Details](/docs/appeventapi#appallroute-workerfunction)
## **Middleware & Authentication APIs**
- **`use(function)`** - Register global middleware β [Details](/docs/appeventapi#appuseworkerfunction)
- **`auth(path, function)`** - Register authentication middleware β [Details](/docs/appeventapi#appauthroute-workerfunction)
- **`static(options, function?)`** - Serve static files from source code β [Details](/docs/appeventapi#appstaticoptions-callback)
- **`storage(options, function?)`** - Serve files from blob storage β [Details](/docs/appeventapi#appstorageoptions-callback)
## **Database/NoSQL APIs**
### **Connection & Setup**
- **`Datastore.open()`** - Connect to datastore and return API interface β [Details](/docs/nosql-database-api#datastoreopen)
### **Document Operations**
- **`insertOne(collection, document)`** - Insert new document β [Details](/docs/nosql-database-api#insertonecollection-document)
- **`getOne(collection, query)`** - Get single document by ID/query β [Details](/docs/nosql-database-api#getonecollection-id--query)
- **`findOne(collection, query)`** - Alias for getOne β [Details](/docs/nosql-database-api#findonecollection-id--query)
- **`getMany(collection, query?, options?)`** - Get stream of documents β [Details](/docs/nosql-database-api#getmanycollection-query-options)
- **`find(collection, query?, options?)`** - Alias for getMany β [Details](/docs/nosql-database-api#findcollection-query-options)
- **`toArray(collection, query?, options?)`** - Get documents as array β [Details](/docs/nosql-database-api#toarraycollection-query-options)
- **`updateOne(collection, query, document, operators?, options?)`** - Update document (patch) β [Details](/docs/nosql-database-api#updateonecollection-id--query-updateoperators-options)
- **`updateMany(collection, query, document, operators?, options?)`** - Update multiple documents β [Details](/docs/nosql-database-api#updatemanycollection-query-document-options)
- **`replaceOne(collection, query, document, options?)`** - Replace document completely β [Details](/docs/nosql-database-api#replaceonecollection-id--query-document-options)
- **`replaceMany(collection, query, document, options?)`** - Replace multiple documents β [Details](/docs/nosql-database-api#replacemanycollection-query-document-options)
- **`removeOne(collection, query)`** - Remove document β [Details](/docs/nosql-database-api#removeonecollection-id--query)
- **`removeMany(collection, query, options?)`** - Remove multiple documents β [Details](/docs/nosql-database-api#removemanycollection-query-options)
### **Schema Management**
- **`setSchema(collection, schema)`** - Add JSON-Schema validation β [Details](/docs/nosql-database-api#setschemacollection-schema)
- **`getSchema(collection)`** - Get collection schema β [Details](/docs/nosql-database-api#getschemacollection)
- **`removeSchema(collection)`** - Remove JSON-Schema validation β [Details](/docs/nosql-database-api#removeschemacollection)
### **Collection Management**
- **`createCollection(collection, options?)`** - Create new collection β [Details](/docs/nosql-database-api#createcollectioncollection-options)
- **`dropCollection(collection)`** - Delete collection β [Details](/docs/nosql-database-api#dropcollectioncollection)
## **Key-Value Store APIs**
- **`set(key, value, options?)`** - Set key-string value pair with optional TTL β [Details](/docs/key-value-database-api#setkey-value-options)
- **`setObj(key, value, options?)`** - Set key-object pair with optional TTL β [Details](/docs/key-value-database-api#setobjkey-value-options)
- **`get(key, options?)`** - Get string value by key β [Details](/docs/key-value-database-api#getkey-options)
- **`getObj(key, options?)`** - Get object value by key β [Details](/docs/key-value-database-api#getobjkey-options)
- **`getAll(keyPattern, options?)`** - Get all key-value pairs matching pattern β [Details](/docs/key-value-database-api#getallkeypattern-options)
- **`del(key, options?)`** - Delete key-value pair β [Details](/docs/key-value-database-api#delkey-options)
- **`delAll(key, options?)`** - Delete all key-value pairs matching pattern β [Details](/docs/key-value-database-api#delallkey-options)
- **`incr(key, value, options?)`** - Increment numeric value β [Details](/docs/key-value-database-api#incrkey-number-options)
- **`decr(key, value, options?)`** - Decrement numeric value β [Details](/docs/key-value-database-api#decrkey-number-options)
## **Queue & Background Processing APIs**
- **`queue(topic, function)`** - Register queue handlers β [Details](/docs/queuehooks)
- **`worker(name, function)`** - Register worker functions β [Details](/docs/appeventapi#appworkername-workerfunction)
- **`enqueue(topic, document, options?)`** - Add job to queue β [Details](/docs/queueapi#enqueuetopic-payload-options)
- **`enqueueFromQuery(collection, query, topic, options?)`** - Queue items from database query β [Details](/docs/queueapi#enqueuefromquerycollection-query-topic-options)
## **Job Scheduling APIs**
- **`job(cronExpression, function)`** - Register scheduled cron jobs β [Details](/docs/jobhooks#cron-jobs)
- **`schedule.runAt(when, data, worker)`** - Schedule one-time delayed job β [Details](/docs/jobhooks#schedule-delayed-worker-function-dynamically-with-runat)
- **`schedule.run(data, worker)`** - Execute worker immediately β [Details](/docs/jobhooks#schedule-worker-function-to-run-immediate)
## **Workflow APIs**
### **Workflow Management**
- **`createWorkflow(name, description, steps)`** - Create new workflow definition β [Details](/docs/workflow-api#createworkflowname-description-steps)
- **`start(initialState)`** - Start new workflow instance β [Details](/docs/workflow-api#startinitialstate)
- **`updateState(instanceId, state, options?)`** - Update workflow state β [Details](/docs/workflow-api#updatestateinstanceid-state-options)
- **`setState(instanceId, stateData)`** - Set complete workflow state β [Details](/docs/workflow-api#setstateinstanceid-statedata)
- **`continue(instanceId, reset?)`** - Continue paused workflow β [Details](/docs/workflow-api#continueinstanceid-reset)
- **`getWorkflowStatus(id)`** - Get workflow status β [Details](/docs/workflow-api#getworkflowstatusid)
- **`getInstances(filter)`** - List workflow instances β [Details](/docs/workflow-api#getinstancesfilter)
- **`cancelWorkflow(id)`** - Cancel workflow instance β [Details](/docs/workflow-api#cancelworkflowid)
### **Error Recovery & Monitoring**
- **`continueAllTimedOut()`** - Continue all timed out workflows β [Details](/docs/workflow-api#continuealltimedout)
- **`findTimedOutSteps(filter?)`** - Find timed out workflow steps β [Details](/docs/workflow-api#findtimedoutstepsfilter)
### **Event Management**
- **`on(event, listener)`** - Register event listener β [Details](/docs/workflow-api#onevent-listener)
- **`once(event, listener)`** - Register one-time event listener β [Details](/docs/workflow-api#onceevent-listener)
- **`off(event, listener)`** - Remove event listener β [Details](/docs/workflow-api#offevent-listener)
- **`emit(event, data)`** - Emit custom event β [Details](/docs/workflow-api#emitevent-data)
### **Configuration**
- **`configure(options)`** - Configure workflow settings β [Details](/docs/workflow-api#configureoptions)
## **File Management APIs**
- **`filestore.readFile(path)`** - Read file content as text β [Details](/docs/fileapi#readfilepath)
- **`filestore.readFileAsBuffer(path)`** - Read file content as buffer β [Details](/docs/fileapi#readfileasbufferpath)
- **`filestore.getReadStream(path)`** - Read file as binary stream β [Details](/docs/fileapi#getreadstreampath)
- **`filestore.saveFile(path, filestream)`** - Write binary stream to file β [Details](/docs/fileapi#savefilepath-filestream)
- **`filestore.deleteFile(path)`** - Delete file β [Details](/docs/fileapi#deletefilepath)
- **`filestore.list(path)`** - List files in directory β [Details](/docs/fileapi#listpath-options)
## **Real-time Communication APIs**
- **`realtime.createChannel(channel)`** - Create real-time channel β [Details](/docs/realtimeapi#realtimecreatechannelchannel)
- **`realtime.publishEvent(channel, data, query?)`** - Publish event to channel β [Details](/docs/realtimeapi#realtimepublisheventchannel-data-query)
- **`realtime.createListener(channel, data)`** - Create listener for channel β [Details](/docs/realtimeapi#realtimecreatelistenerchannel-data)
- **`realtime.getListener(channel, listenerID)`** - Get specific listener β [Details](/docs/realtimeapi#realtimegetlistenerchannel-listenerid)
- **`realtime.getListeners(channel)`** - Get all listeners for channel β [Details](/docs/realtimeapi#realtimegetlistenerschannel)
- **`realtime.removeListener(channel, listenerID)`** - Remove listener β [Details](/docs/realtimeapi#realtimeremovelistenerchannel-listenerid)
## **Template & Configuration APIs**
- **`set(key, val)`** - Set application configuration β [Details](/docs/appeventapi)
- **`crudlify(schema?, options?)`** - Auto-generate CRUD REST API β [Details](/docs/appeventapi#appcrudlifyschema-options)
## **Application Lifecycle APIs**
- **`init(function?)`** - Initialize application and return manifest β [Details](/docs/appeventapi#appinit)
- **`start(function?)`** - Alias for init() method β [Details](/docs/appeventapi#appinit)
## **Database REST API Endpoints**
When using `crudlify()`, these endpoints are automatically created:
| Operation | Method | Endpoint | Description |
| --------- | -------- | -------------------- | ---------------------------------------------------------------------------------- |
| List all | `GET` | `/:collection` | Get all documents β [Details](/docs/database-rest-api#get-list-of-documents) |
| Query | `GET` | `/:collection?query` | Get documents by query β [Details](/docs/database-rest-api#get-documents-by-query) |
| Get by ID | `GET` | `/:collection/:id` | Get single document β [Details](/docs/database-rest-api#get-document-by-id) |
| Create | `POST` | `/:collection` | Create new document β [Details](/docs/database-rest-api#create-a-new-document) |
| Update | `PATCH` | `/:collection/:id` | Update document β [Details](/docs/database-rest-api#update-a-document-by-id) |
| Replace | `PUT` | `/:collection/:id` | Replace document β [Details](/docs/database-rest-api#replace-a-document-by-id) |
| Delete | `DELETE` | `/:collection/:id` | Delete document β [Details](/docs/database-rest-api#delete-a-document-by-id) |
## **Authentication Methods**
- **API Tokens** - Use `x-apikey` header for app-to-app authentication β [Details](/docs/authentication#app-to-app-authentication-with-api-tokens)
- **JWT/JWKS** - Configure JWT authentication via Auth0, Clerk, etc β [Details](/docs/authentication#authenticate-users-with-jwt-using-jwks)
- **Custom Auth** - Use `codehooks-auth` package for complete control β [Details](/docs/authentication#custom-authentication-with-codehooks-auth)
## **Query Syntax Examples**
### NoSQL Queries
```javascript
// Simple equality
{
status: 'active';
}
// Comparison operators
{
age: {
$gt: 18;
}
}
{
price: {
$lte: 100;
}
}
// Logical operators
{
$and: [{ status: 'active' }, { age: { $gte: 18 } }];
}
// Array operations
{
tags: {
$in: ['javascript', 'nodejs'];
}
}
// Regular expressions
{
name: {
$regex: /john/i;
}
}
```
### URL Query Parameters
```bash
# Simple query
?status=active&limit=10
# Advanced query
?q={"age":{"$gt":18}}&sort=name&fields=name,email
```
## **Common Usage Patterns**
### Basic API Setup
```javascript
import { app } from 'codehooks-js';
// Auto-generate CRUD API
app.crudlify();
// Custom routes
app.get('/hello', (req, res) => {
res.json({ message: 'Hello World' });
});
export default app.init();
```
### Database Operations
```javascript
import { Datastore } from 'codehooks-js';
async function myFunc() {
const conn = await Datastore.open();
// Insert document
const doc = await conn.insertOne('users', { name: 'John' });
// Query documents
const users = await conn.getMany('users', { active: true }).toArray();
// Key-value operations
await conn.set('session:123', { userId: 'abc' }, { ttl: 3600000 });
}
...
```
### Background Processing
```javascript
// Register worker
app.worker('emailWorker', async (req, res) => {
console.log('Processing email:', req.body.payload);
res.end();
});
// Queue job
async function myFunc() {
const conn = await Datastore.open();
await conn.enqueue('emailWorker', { email: 'user@example.com' });
}
...
```
### Workflow Processing
```javascript
// Create a workflow with persistent state
const workflow = app.createWorkflow(
'orderProcessing',
'Process customer orders',
{
start: async (state, goto) => {
state.orderId = state.orderId || generateOrderId();
goto('validatePayment', state);
},
validatePayment: async (state, goto) => {
// Validate payment logic here
if (state.paymentValid) {
goto('fulfillOrder', state);
} else {
goto('handlePaymentError', state);
}
},
fulfillOrder: async (state, goto) => {
state.status = 'fulfilled';
goto(null, state); // Complete workflow
},
}
);
// Start workflow instance
const orderWorkflow = await workflow.start({
customerId: '123',
amount: 99.99,
});
```
---
**Links:**
- [Complete Documentation](/docs)
- [Getting Started Guide](/docs/quickstart-cli)
- [Code Examples](/docs/examples/examples-overview)
- [CLI Reference](/docs/cli)
- [Prompt for development using ChatGPT, Claude, Cursor etc](/docs/chatgpt-backend-api-prompt)
- [MCP Server for Agent based development](https://github.com/RestDB/codehooks-mcp-server)
---
## The Application Object
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
The Application object creates events that lets you hook your serverless functions accordingly.
```mermaid
flowchart LR
event("REST API events") & job("Background events") --> hook("App hook") --> func("Function") -.- db[(Database)]
```
Available event types are:
- REST API route events
- Background Job cron and scheduled events
- Worker Queue events
- Authentication events
The event hooks is an Node.js Express style JavaScript library in a [public npm library](https://www.npmjs.com/package/codehooks-js) that you install as a npm package in your project directory. You can check for updates in the npm version.
Install the CLI.
```bash
npm i codehooks-js
```
Deploy your app with the CLI command.
```bash
coho deploy
```
**API quick overview**
## Complete code example
Import the `app` library in your application and hook into the various events.
```js
import { app } from 'codehooks-js';
// generic serverless function
function fooFunc(req, res) {
res.end();
}
// generic middleware function
function fooMiddleware(req, res, next) {
next();
}
/*
* Hook into events
*/
app.use(fooMiddleware); // global middleware
app.use('/foo', fooMiddleware); // global middleware on routes
app.get('/foo', fooFunc); // GET
app.post('/foo', fooFunc); // POST
app.put('/foo', fooFunc); // PUT
app.patch('/foo', fooFunc); // PATCH
app.delete('/foo', fooFunc); // DELETE
app.all('/foo', fooFunc); // GET, POST, PUT, PATCH, DELETE
app.auth('/*', fooMiddleware); // Called before outher routes without access tokens
app.job('* * * * *', fooFunc); // subscribe to cron events
app.worker('workername', fooFunc); // subscribe to worker events
app.static(options); // serve deployed static files and content
app.storage(options); // serve dynamic uploaded files and content
app.crudlify(options); // Database CRUD REST API
// Bind events and functions to the serverless runtime
export default app.init();
```
## app.init()
Mandatory call to bind your functions to events from the serverless runtime.
**Parameters** none
## app.use(workerFunction)
Adds global middleware to all route API events (get, put, post, patch, delete).
**Parameters**
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object), [next](rest-api-app-routes#middleware-functions))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
import cookieParser from 'cookie-parser';
// external npm lib middleware
app.use(cookieParser());
app.get('/foo', function (req, res) {
// Cookies that have not been signed
console.log('Cookies: ', req.cookies);
// Cookies that have been signed
console.log('Signed Cookies: ', req.signedCookies);
// Custom middleware result
console.log('Foo: ', req.foo);
res.send('Done');
});
export default app.init();
```
## app.use(route, workerFunction)
Adds global middleware to a specific route API events (get, put, post, patch, delete).
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object), [next](rest-api-app-routes#middleware-functions))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
// custom
app.use('/foo', (req, res, next) => {
console.log('Called before any other route handlers');
req.foo = 'Foo was here!';
next(); // must be called to continue
});
app.get('/foo', function (req, res) {
// Custom middleware result
console.log('Foo: ', req.foo);
res.send('Done');
});
export default app.init();
```
## app.get(route, workerFunction)
Execute workerFunction function on HTTP GET method and route match.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
app.get('/foo', function (req, res) {
res.send('Done');
});
export default app.init();
```
## app.post(route, workerFunction)
Execute workerFunction function on HTTP POST method and route match.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
app.post('/foo', function (req, res) {
console.log('Data from client is', req.body);
res.send('Done');
});
export default app.init();
```
## app.put(route, workerFunction)
Execute workerFunction function on HTTP PUT method and route match.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
app.put('/foo', function (req, res) {
console.log('Data from client is', req.body);
res.send('Done');
});
export default app.init();
```
## app.patch(route, workerFunction)
Execute workerFunction function on HTTP PATCH method and route match.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
app.patch('/foo', function (req, res) {
console.log('Data from client is', req.body);
res.send('Done');
});
export default app.init();
```
## app.delete(route, workerFunction)
Execute workerFunction function on HTTP DELETE method and route match.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
app.delete('/foo/:ID', function (req, res) {
const { ID } = req.params;
console.log('Delete this', ID);
res.send('Done');
});
export default app.init();
```
## app.all(route, workerFunction)
If no other routes matched before this, then execute the workerFunction function on all HTTP methods and route match.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
app.get('/foo', function (req, res) {
res.send('Done GET');
});
app.all('/*', function (req, res) {
res.send(
`None of the above routes matched but this ${req.method} ${req.originalUrl}`
);
});
export default app.init();
```
## app.auth(route, workerFunction)
Execute workerFunction function when no authentication token (x-apikey or JWT) is present.
Works for all HTTP methods and route matches.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
[See Auth hooks docs here.](authhooks)
## app.worker(name, workerFunction)
Register a named workerFunction function that can be used to process queued events or scheduled events.
**Parameters**
- **name**: unique worker name
- **workerFunction**: function(queueData, responseFunction)
**Returns** void
**Code example for worker queues**
[See Queue docs here.](queuehooks)
**Code example for scheduled workers**
[See scheduled jobs docs here.](jobhooks#schedule-delayed-worker-function-dynamically-with-runat)
## app.job(cronExpression, workerFunction)
Execute workerFunction function when cron job are scheduled.
**Parameters**
- **cronExpression**: a valid cron expression string
- **workerFunction**: function(jobData, responseFunction)
[See Job hook docs here.](jobhooks)
## app.static(options, callback)
Serve static files from deployed source directory.
For example: `app.static({ route: '/img', directory: '/assets/images' })`
**Options**
- **directory**: path to file upload directory
- **route**: URL sub route for clients
**callback(req, res, next)**
Provide a middleware function to control the request.
For example, add client side cache headers instructions for static assets.
```js
// serve static assets/images, and cache for 1 hour
app.static({ route: '/assets', directory: '/assets' }, (req, res, next) => {
console.log(
'If you see this, the client cache is invalidated or called for the first time'
);
const ONE_HOUR = 1000 * 60 * 60;
res.set('Cache-Control', `public, max-age=${ONE_HOUR}, s-maxage=${ONE_HOUR}`);
res.setHeader('Expires', new Date(Date.now() + ONE_HOUR).toUTCString());
res.removeHeader('Pragma');
next();
});
```
## app.storage(options, callback)
Serve files from the blob storage file system. You can upload files to the blob storage with the CLI.
Also see the [Filesystem API docs](/docs/fileapi).
```bash
$ coho file-upload --projectname 'myproject-fafc' --space 'dev' --src '/mylocaldir/' --target '/mydocuments'
```
For example: `app.storage({ route: '/docs', directory: '/mydocuments' })`
**Options**
- **directory**: path to file upload directory
- **route**: URL sub route for clients
**callback(req, res, next)**
Provide a middleware function to control the request.
## app.crudlify(schema, options)
Creates a complete Database REST API ([detailed docs here](/docs/database-rest-api)).
Example: REST API for two collection `customer` and `product`, any schema is allowed:
```js
app.crudlify({ customer: {}, product: {} }, { prefix: '/crudapi' });
// Example valid URL's
// https://myproject-ff00.api.codehooks.io/dev/customer?name=Bill
// https://myproject-ff00.api.codehooks.io/dev/product?price>42
```
**Schema**
-- **collection**: schema, for specific collections, e.g. `{"collection": someSchema}`
**Options**
- **prefix**: serve REST routes under another route. `{"prefix": "/api"}`
**Returns** Promise to `before` `after` functions.
Example:
```js
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
})
```
## app.createWorkflow(name, desc, workflowJSON)
Create reliable stateful workflows using plain JavaScript.
**Parameters**
- **name** (string): Unique identifier for the workflow
- **description** (string): Human-readable description of the workflow
- **steps** (WorkflowDefinition): Object containing step definitions
**Returns**: Workflow instance for managing the workflow
**Code example**:
```js
import { app } from 'codehooks-js';
// The workflow definition
const workflow = app.createWorkflow('minimal', 'Minimal workflow example', {
start: (state, goto) => {
goto('end', { ...state, message: 'Hello World' });
},
end: (state, goto) => goto(null, state), // null complete the workflow
});
// A REST API to start a new workflow instance
app.post('/start', async (req, res) =>
res.json(await workflow.start({ prop: 'some value' }))
);
export default app.init();
```
Read more in the [detailed workflow API docs](/docs/workflow-api)
---
## Authentication
*(Note: This content contains MDX/JSX code)*
Application APIs and data can be accessed with a secure token.
Tokens can be JWT tokens created from a provider such as Auth0.com or API tokens created by yourself (or any project user with ADMIN rights).
In addition, some CLI commands can use a special _admin token_ (se below).
## App-to-app authentication with API tokens
### Create an API token with the CLI
API tokens can be created i two ways.
1. Using the CLI command `coho add-token`
2. Using the admin application at https://account.codehooks.io
The example below shows how to use the CLI to create a new `READ` access API token.
```bash
coho add-token --readonly
Created new token 5f1ce782-5798-4295-bdd0-51568390a59c with access READ
```
The API token can now be used to access your application API.
Add the `x-apikey` as a HTTP header in the request.
```bash
curl https://myproject-ff00.api.codehooks.io/dev/myapi \
-H 'x-apikey: 5f1ce782-5798-4295-bdd0-51568390a59c'
```
:::warning note
The secure token should not be used from web pages. We recommend that you only use it server side.
:::
### Using the web application to add an API token
In your account at https://account.codehooks.io, find the project and the space settings to add a new token.

## Authenticate users with JWT (using JWKS)
### Set up JWKS with the CLI
If you have set up JWT authentication using [Auth0.com (Okta)](https://auth0.com), [Clerk](https://clerk.com/), [Stack-Auth](https://stack-auth.com) or similar services, you can easily set up a JWKS endpoint in the space settings. All API calls will then be verified using this mechanism.
```bash
# add JWKS endpoint
$ coho jwks https://.auth0.com/.well-known/jwks.json
Set JWKS url to space 'dev'
```
| Auth provider | JWKS URL |
| -------------- | ----------------------------------------------------------------------------------------------- |
| Auth0.com | `https://.auth0.com/.well-known/jwks.json` |
| Clerk.com | `https:///.well-known/jwks.json` |
| Stack-auth.com | `https://api.stack-auth.com/api/v1/projects//.well-known/jwks.json` |
### Using the web application to set a JWKS endpoint
In your account at https://account.codehooks.io, find the project and the space settings to set the JWKS endpoint URL.

:::tip
You can also roll your own authentication using code. Read more about using [authhooks](/docs/authhooks) for this.
:::
:::info Verifying JWT tokens - RS256 vs HS256
By default, to verify login tokens (JWT), Auth0 example applications now use the asymmetric RS256 algorithm instead of the HS256 symmetric one. What is the difference? HS256 requires you to use a secret key to verify the token but RS256 let you use a public key which can be fetched from an URL on the web (JWKS URL). You can read more about it in [this blogpost by Auth0.](https://auth0.com/blog/navigating-rs256-and-jwks/)
:::
## Use admin tokens to authenticate with the CLI
For use with the CLI, you can create admin tokens instead of having to log in to your account as a user with ADMIN rights. Admin tokens belong to the personal account or a team and applies to the following codehooks CLI commands: deploy, undeploy, createindex, removeindex, backup, restore, import, export, query.
Admin tokens are ideal for use in shell scripts and for CI purposes.
```bash
# add admin token
$ coho add-admintoken
? Select personal account or team to add admin token to Personal
New admin token added: 0882e570d8fc7fe97ae958ae8df3d7ba-7f5c5eb7177c
Please copy this now and keep it secret. It will never be shown in full again.
```
```bash
# use the admin token
$ coho deploy --admintoken 0882e570d8fc7fe97ae958ae8df3d7ba-7f5c5eb7177c
Deploying to Project: students-3bfe Space: dev
Deployed Codehook successfully! π
```
## Use IP address to limit API access
From your serverless functions you can inspect the client IP address from the request header fields.
The example headers listen below shows that the client IP address is `11.22.33.44`.
```js
headers: {
host: 'myapp-f0gt.api.codehooks.io',
'x-request-id': 'ba5582e3bafd12602b3f70a5af5ab20f',
'x-real-ip': '11.22.33.44',
'x-forwarded-for': '11.22.33.44',
'x-forwarded-host': 'myapp-f0gt.api.codehooks.io',
'x-forwarded-port': '443',
'x-forwarded-proto': 'https',
'x-forwarded-scheme': 'https',
'x-scheme': 'https',
accept: '*/*',
'user-agent': 'Thunder Client (https://www.thunderclient.com)',
'x-api-key': '07ee0bba-xxxx-yyy-zzzz-dfd0b991fa0a'
}
```
Use the [CLI command `coho whitelist`](/docs/cli.md#whitelist) or the admin web application to give exclusive IP addresses API access, e.g.
`coho whitelist 11.22.33.44`
You can use a [authentication hook](/docs/authhooks) to intercept API requests.
## Custom Authentication with codehooks-auth
For complete control over authentication, you can use the open-source [`codehooks-auth`](https://github.com/RestDB/codehooks-auth) package. This solution provides:
- JWT-based authentication with access and refresh tokens
- Built-in user management with OAuth support (Google and GitHub)
- Email verification and OTP (One-Time Password) authentication
- Customizable templates and email notifications

### Setup Instructions
1. Install the required packages:
```bash
npm install codehooks-auth codehooks-js
```
2. Set up JWT secrets:
```bash
coho set-env JWT_ACCESS_TOKEN_SECRET 'your_access_token_secret' --encrypted
coho set-env JWT_REFRESH_TOKEN_SECRET 'your_refresh_token_secret' --encrypted
```
3. Configure authentication in your backend code:
```js
import { app } from 'codehooks-js';
import { initAuth } from 'codehooks-auth';
const settings = {
JWT_ACCESS_TOKEN_SECRET: process.env.JWT_ACCESS_TOKEN_SECRET,
JWT_REFRESH_TOKEN_SECRET: process.env.JWT_REFRESH_TOKEN_SECRET,
redirectSuccessUrl: '/dashboard.html', // where to redirect after successful login
baseAPIRoutes: '/api', // protected routes
// Optional: Configure OAuth providers
google: {
CLIENT_ID: process.env.CLIENT_ID,
CLIENT_SECRET: process.env.CLIENT_SECRET,
REDIRECT_URI:
'https://{YOUR_APP_URL}.codehooks.io/auth/oauthcallback/google',
},
github: {
CLIENT_ID: process.env.GITHUB_CLIENT_ID,
CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
REDIRECT_URI:
'https://{YOUR_APP_URL}.codehooks.io/auth/oauthcallback/github',
},
};
// Initialize auth with your settings
initAuth(app, settings);
export default app.init();
```
4. Access protected routes in your client code:
```js
fetch('/api/protected-route', {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
});
```
The authentication system will automatically handle:
- User registration and login flows
- JWT token management (access and refresh tokens)
- OAuth authentication with supported providers
- Email verification and password reset functionality
- Protected API route authorization
See the [codehooks-auth documentation](https://github.com/RestDB/codehooks-auth) for complete setup instructions, template customization options, and advanced configuration.
---
## Auth hooks
*(Note: This content contains MDX/JSX code)*
Auth hooks lets you override the default security behaviour on specific routes.
If an auth hook route matches a route hook, and no authentication token is present in the headers, the auth function is called as a last resort to allow or dismiss the request.
The auth hook function logic calls `next()` to allow, or `response.end()` to block a client request.
Use the auth hook to create public routes and custom rules and overrides to fit any use case.
## Example auth hook
```javascript {9} title="index.js"
import app from 'codehooks-js'; // Standard JS lib for express style code
// REST API routes
app.get('/specialroute/frags', (req, res) => {
res.end('You have the correct secret header value');
});
// Auth hook
app.auth('/specialroute/*', (req, res, next) => {
// call some auth function here, e.g. myLookup
myLookup(req.headers['X-challenge'], (err, data) => {
if (err) {
res.status(401); // Unauthorized
res.end();
} else {
// allow API call
next();
}
});
});
function myLookup(challenge, callback) {
if (challenge === 'SOMESECRET') {
callback(null);
} else {
callback('Sorry');
}
}
export default app.init(); // Bind functions to the serverless runtime
```
[See docs for route matching](rest-api-app-routes#route-matching)
[See docs for middleware functions and the next() function](rest-api-app-routes#middleware-functions)
---
## Codehooks CLI tool
*(Note: This content contains MDX/JSX code)*
Command line interface (CLI) for codehooks.io ([npm package](https://www.npmjs.com/package/codehooks)).
**Version**: `1.2.20`
The CLI lets you manage projects and spaces (environments) (serverless functions + datastore + settings) from the command line.
## Popular commands
- New Project: `coho create myproject`
- Start coding: `mkdir myproject && cd myproject && coho init myproject-f2a0 --empty`
- Show project info: `coho info`
- New Space (environment): `coho add myspace`
- New Collection: `coho createcoll 'customers'`
- Import data: `coho import -f '/home/myfile.csv' -c 'customers' --separator ','`
- Invite: `coho invite jane@example.com myproject`
- Deploy your app: `coho deploy`
- Real time logs: `coho logs -f`
- Query the datastore: `coho query --collection products --query 'type=soft'`
- Upload static files: `coho upload './assets'`
## Install
```sh
$ npm i codehooks -g
```
## Usage
```sh
$ codehooks --help
```
Help output:
```
codehooks
Commands:
codehooks admin Open the codehooks.io admin account UI at account.codehooks.io
codehooks docs Open the codehooks.io documentation
codehooks login [provider] Authenticate CLI - sign up and/or sign in
codehooks logout Log out of the CLI
codehooks account Show info about account, projects and invitations
codehooks invite [email] [projectname] Invite user to project
codehooks join [projectname] Join project
codehooks leave [projectname] Leave project
codehooks create [projectname] [teamid] [description] Create and initialize a new codehooks project [aliases: init]
codehooks init [projectname] [space] Initialise an existing project space in the current folder. Sets up default CRUD template in index.js.
codehooks add [space] [projectname] [restricted] Add new space to project
codehooks use [name] Set active space
codehooks info [projectname] [space] Show info about project and spaces
codehooks stats [project] Show usage metrics for project spaces
codehooks file-upload [src] [target] Upload files to server [aliases: up, upload]
codehooks file-delete [filename] Delete a file from server [aliases: delete]
codehooks file-list [path] List files from server [aliases: files]
codehooks verify [dir] Compile code (defaults to current dir) [aliases: compile, ver, comp]
codehooks deploy Deploys current codehook folder [aliases: de, dep]
codehooks undeploy Undeploy current codehook folder [aliases: unde, undep]
codehooks install [template] Install a template application [aliases: inst, i]
codehooks log [space] [tail] [follow] [context] Show system logs for a space. [aliases: logs]
codehooks count [collection] [space] Count objects in a collection in current space [aliases: co]
codehooks query [collection] [query] Run query on collection in current space [aliases: q]
codehooks createindex [collection] [index] [space] Add field(s) to a query index [aliases: index, idx, create-index]
codehooks dropindex [collection] [index] [space] Remove field(s) from a query index [aliases: removeindex, remove-index, rmindex, delete-index]
codehooks get [key] [keyspace] [space] [text] [jsonl] [onlycount] Retrieve key-value pair(s) from a space
codehooks set [key] [val] Set key-value pair in a space
codehooks del [key] [keyspace] [space] [json] Delete key-value pair in a space
codehooks collection [project] [output] Show collections for space [aliases: coll, col, ls]
codehooks createcollection [collection] Create a new collection [aliases: createcoll, add-collection]
codehooks dropcollection [collection] Delete all data in collection and remove collection name [aliases: dropcoll, rmcoll, deletecoll]
codehooks add-schema [collection] [schema] Add a JSON schema to a collection [aliases: schema, create-schema]
codehooks remove-schema [collection] Remove JSON schema for a collection [aliases: delete-schema, del-schema]
codehooks cap-collection [collection] [cap] [capdelay] Cap a collection [aliases: cap, cap-coll, capcoll]
codehooks uncap-collection [collection] Remove cap on a collection [aliases: uncap]
codehooks import [filepath] [collection] [project] [space] [dryrun] Import JSON or CSV data from file [aliases: imp]
codehooks export [collection] [project] [space] Export JSON or CSV data
codehooks add-token [readonly] Add token to space
codehooks remove-token [token] Remove token from space
codehooks set-env [key] [value] [encrypted] Set environment variable for space
codehooks remove-env [key] Remove environment variable from space
codehooks jwks [url] Set/replace JWKS endpoint for OAuth2 authentication. Set to "" (empty string) to remove.
codehooks whitelist [ip] Add host to whitelist (use to restrict space access)
codehooks whitelist-remove [ip] Remove host from whitelist
codehooks completion Generate command completion script. Just add this to your .bashrc, .bash_profile, .zshrc (or similar) on *nix machines
codehooks remove-project [projectname] Remove the project
codehooks remove-space [space] [projectname] Remove space and data
codehooks admintokens Show active admintokens for account or team
codehooks add-admintoken Add admin token to account or team (for use with CI)
codehooks remove-admintoken Remove admin token from account or team
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
No project active in this folder! Use 'coho create' or 'codehooks create' to create a new project.
created by Codehooks AS - info@codehooks.io
***DEVELOPMENT***
```
## Available commands
- [admin](#admin)
- [docs](#docs)
- [login](#login)
- [logout](#logout)
- [account](#account)
- [invite](#invite)
- [join](#join)
- [leave](#leave)
- [create](#create)
- [init](#init)
- [add](#add)
- [use](#use)
- [info](#info)
- [stats](#stats)
- [file-upload](#file-upload)
- [file-delete](#file-delete)
- [file-list](#file-list)
- [verify](#verify)
- [deploy](#deploy)
- [undeploy](#undeploy)
- [install](#install)
- [log](#log)
- [count](#count)
- [query](#query)
- [createindex](#createindex)
- [dropindex](#dropindex)
- [get](#get)
- [set](#set)
- [del](#del)
- [collection](#collection)
- [createcollection](#createcollection)
- [dropcollection](#dropcollection)
- [add-schema](#add-schema)
- [remove-schema](#remove-schema)
- [cap-collection](#cap-collection)
- [uncap-collection](#uncap-collection)
- [import](#import)
- [export](#export)
- [add-token](#add-token)
- [remove-token](#remove-token)
- [set-env](#set-env)
- [remove-env](#remove-env)
- [jwks](#jwks)
- [whitelist](#whitelist)
- [whitelist-remove](#whitelist-remove)
- [completion](#completion)
- [remove-project](#remove-project)
- [remove-space](#remove-space)
- [admintokens](#admintokens)
- [add-admintoken](#add-admintoken)
- [remove-admintoken](#remove-admintoken)
### admin
```sh
$ codehooks admin --help
```
Help output:
```
codehooks admin
Open the codehooks.io admin account UI at account.codehooks.io
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### docs
```sh
$ codehooks docs --help
```
Help output:
```
codehooks docs
Open the codehooks.io documentation
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### login
```sh
$ codehooks login --help
```
Help output:
```
codehooks login [provider]
Authenticate CLI - sign up and/or sign in
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### logout
```sh
$ codehooks logout --help
```
Help output:
```
codehooks logout
Log out of the CLI
Options:
-d, --debug show debug (verbose) information
--dir [default: "."]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### account
```sh
$ codehooks account --help
```
Help output:
```
codehooks account
Show info about account, projects and invitations
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### invite
```sh
$ codehooks invite --help
```
Help output:
```
codehooks invite [email] [projectname]
Invite user to project
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
-t, --email [string] [required]
--role [string] [default: "ADMIN"]
--remove remove invitation [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### join
```sh
$ codehooks join --help
```
Help output:
```
codehooks join [projectname]
Join project
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### leave
```sh
$ codehooks leave --help
```
Help output:
```
codehooks leave [projectname]
Leave project
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### create
```sh
$ codehooks create --help
```
Help output:
```
codehooks create [projectname] [teamid] [description]
Create and initialize a new codehooks project
Options:
-d, --debug show debug (verbose) information
--description A few words about this project
-n, --projectname Project name
-t, --teamid Add project to team with this id
-g, --ga-deploy Add Github Action for automatic deploy [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### init
```sh
$ codehooks init --help
```
Help output:
```
codehooks init [projectname] [space]
Initialise an existing project space in the current folder. Sets up default CRUD template in index.js.
Options:
-d, --debug show debug (verbose) information
-n, --projectname Project name
-s, --space Space (environment) name [default: "dev"]
-e, --empty Only create the project config file [boolean]
--download Download source code from project [boolean] [default: true]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks init
codehooks init --empty
codehooks init --download
```
### add
```sh
$ codehooks add --help
```
Help output:
```
codehooks add [space] [projectname] [restricted]
Add new space to project
Options:
-d, --debug show debug (verbose) information
--projectname [required]
-n, --space space name [required]
--restricted only team admins or owner can use if restricted [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### use
```sh
$ codehooks use --help
```
Help output:
```
codehooks use [name]
Set active space
Options:
-d, --debug show debug (verbose) information
--projectname [required]
-n, --name
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### info
```sh
$ codehooks info --help
```
Help output:
```
codehooks info [projectname] [space]
Show info about project and spaces
Options:
-d, --debug show debug (verbose) information
--projectname Active project [required]
--json Output info as JSON (not table) [boolean]
--space Only show info for this space [string]
-e, --examples Include cURL examples [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### stats
```sh
$ codehooks stats --help
```
Help output:
```
codehooks stats [project]
Show usage metrics for project spaces
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to query
--space Only show info for this space [string]
--json Output info as json [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### file-upload
```sh
$ codehooks file-upload --help
```
Help output:
```
codehooks file-upload [src] [target]
Upload files to server
Options:
-d, --debug show debug (verbose) information
-f, --src Path to directory or file [default: "."]
-t, --target Target directory on server, same as local if not set
-e, --ext Include files matching the extension [string]
--space Select which space (environment) to access
-p, --projectname Select which project name to use
--admintoken Use admin token authentication (use for CI)
--dryrun Output files to upload without performing the action [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks file-upload './static'
codehooks file-upload --src './local' --target '/remote'
codehooks file-upload --src './local' --target '/remote' --ext 'png'
codehooks file-upload --src './local' --target '/remote' --ext 'png|jpg|jpeg|gif'
codehooks file-upload afile.txt
```
### file-delete
```sh
$ codehooks file-delete --help
```
Help output:
```
codehooks file-delete [filename]
Delete a file from server
Options:
-d, --debug show debug (verbose) information
-f, --filename Delete file with match on absolute path/filename
-r, --match Delete multiple file that matches regular expression to a file path
--space Select which space (environment) to access
-p, --projectname Select which project name to use
--admintoken Use admin token authentication (use for CI)
--dryrun Output files to upload without performing the action [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks file-delete --filename '/static/myfile.txt'
codehooks file-delete --match '/static/*'
codehooks file-delete --match '.css$'
```
### file-list
```sh
$ codehooks file-list --help
```
Help output:
```
codehooks file-list [path]
List files from server
Options:
-d, --debug show debug (verbose) information
-f, --path Path to directory or file [default: "."]
--space Select which space (environment) to access
-p, --project Select which project name to use
--admintoken Use admin token authentication (use for CI)
--reverse Scan index in reverse order
--table Output data as formatted table (not JSON) [boolean]
--json Output data as formatted JSON [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks file-list '/static/'
```
### verify
```sh
$ codehooks verify --help
```
Help output:
```
codehooks verify [dir]
Compile code (defaults to current dir)
Options:
-d, --debug show debug (verbose) information
--dir [default: "."]
--space Select which space to access
-p, --projectname Select which project name to use
--admintoken Use admin token authentication (use for CI)
-e, --main Application main file, default is index.js [default: "index"]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### deploy
```sh
$ codehooks deploy --help
```
Help output:
```
codehooks deploy
Deploys current codehook folder
Options:
-d, --debug show debug (verbose) information
--dir [default: "."]
-s, --space Select which space to access
-p, --projectname Select which project name to use
--history Show deployment history
--rollback Undo last deployment, set previous as active
--json Output JSON format
--admintoken Use admin token authentication (use for CI)
--template Deploy a pre defined code template
--upload Upload source code assets to codehooks.io this projects environment [boolean] [default: true]
--compress Compress source code assets before upload [boolean] [default: true]
-e, --main Application main file, default is index.js [default: "index"]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### undeploy
```sh
$ codehooks undeploy --help
```
Help output:
```
codehooks undeploy
Undeploy current codehook folder
Options:
-d, --debug show debug (verbose) information
--dir [default: "."]
-s, --space Select which space to access
-p, --projectname Select which project name to use
--json Output JSON format
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### install
```sh
$ codehooks install --help
```
Help output:
```
codehooks install [template]
Install a template application
Options:
-d, --debug show debug (verbose) information
-t, --template Template directory from Github repo with templates [string] [required]
--space Select which space (environment) to access
-p, --projectname Select which project name to use
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks install 'static-website-tailwincss'
```
### log
```sh
$ codehooks log --help
```
Help output:
```
codehooks log [space] [tail] [follow] [context]
Show system logs for a space.
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project name to use
-s, --space Select which space to log
-t, --tail Chop log to n lines [default: 100]
-f, --follow Keep log stream open
-c, --context Filter log on: jobhooks, queuehooks, routehooks, datahooks, auth
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks log
codehooks log -f
codehooks coho log --tail 10
coho log --project 'pets-ff00' --space prod
```
### count
```sh
$ codehooks count --help
```
Help output:
```
codehooks count [collection] [space]
Count objects in a collection in current space
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to query
-s, --space Select which space to query
-c, --collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### query
```sh
$ codehooks query --help
```
Help output:
```
codehooks query [collection] [query]
Run query on collection in current space
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to query
-s, --space Select which space to query
-q, --query Limit selection with a query expression or JSON string '{...}' [default: ""]
-n, --count Count query results
-c, --collection Collection name [required]
--delete Delete all items from query result
--update Patch all items from query result with JSON string '{...}' [string]
--replace Replace all items from query result with JSON string '{...}' [string]
--useindex Use an indexed field to scan data in query [string]
--start Start value for index scan [string]
--end End value for index scan [string]
--limit Limit query result [number]
--fields Comma separated list of fields to include [string]
--sort Comma separated list of fields to sort by [string]
--offset Skip items before returning data in query result [number]
--enqueue Add query result to queue topic [string]
--pretty Output data with formatting and colors
--reverse Scan index in reverse order
--table Output data as formatted table (not JSON) [boolean]
--csv Output data in CSV format [boolean]
--jsonl Output as JSON Lines (one JSON object per line) [boolean]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks query pets name=Polly
codehooks query --collection pets --query 'name=Polly&type=Parrot'
codehooks query --collection pets --query 'name=/^po/'
codehooks query --collection pets --query 'name=/^po/' --sort 'age,name'
codehooks query pets 'name=Polly' --useindex name --fields 'name,type'
codehooks q pets 'name=Polly&type=Parrot' --update '{"name": "Zilla"}'
codehooks q pets 'type=Fish' --delete
codehooks q pets 'type=Snake' --enqueue 'mytopic'
codehooks q pets --jsonl
codehooks query pets --q '{"type":"Dog"}' --project petsdb-5544 --space dev
```
### createindex
```sh
$ codehooks createindex --help
```
Help output:
```
codehooks createindex [collection] [index] [space]
Add field(s) to a query index
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
-c, --collection Collection with indexes [required]
-i, --index Field to index [required]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks createindex --collection pets --index name
codehooks idx pets name
codehooks idx pets -i name -i type -i 'price-in-dollar'
```
### dropindex
```sh
$ codehooks dropindex --help
```
Help output:
```
codehooks dropindex [collection] [index] [space]
Remove field(s) from a query index
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
-c, --collection Collection with indexes [required]
-i, --index Field to remove index for [required]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks removeindex pets name
codehooks removeindex --collection pets --index 'name' --index 'type'
```
### get
```sh
$ codehooks get --help
```
Help output:
```
codehooks get [key] [keyspace] [space] [text] [jsonl] [onlycount]
Retrieve key-value pair(s) from a space
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to query
-k, --key Key to match, or key* to fetch list [default: "*"]
--keyspace Keyspace to scan
--text Output info as text line [boolean]
--jsonl Output as JSON Lines (one JSON object per line) [boolean]
--onlycount Output only the count of keys [boolean]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks get 'my-value-one'
codehooks get 'my*'
codehooks get '*' --keyspace spacex
codehooks get 'my*' --text
codehooks get 'my*' --jsonl
codehooks get 'my*' --onlycount
```
### set
```sh
$ codehooks set --help
```
Help output:
```
codehooks set [key] [val]
Set key-value pair in a space
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use [string]
-s, --space Select which space to use [string]
--key Key to set [string] [required]
--val Value to set [string] [required]
--keyspace Keyspace to use [string]
--ttl Time to live in millis for value [number]
--json Output info as JSON [boolean]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks set 'my-value-one' 'foo'
codehooks set 'my-value-two' 'bar'
codehooks set 'session-4f51-9bed' 'OK' --keyspace 'spacex' --ttl 60000
```
### del
```sh
$ codehooks del --help
```
Help output:
```
codehooks del [key] [keyspace] [space] [json]
Delete key-value pair in a space
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
--key Key to delete [required]
--keyspace Keyspace to use
--json Output info as JSON [boolean]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks del 'my-value-one'
codehooks del 'my-value-two' --keyspace 'spacex'
```
### collection
```sh
$ codehooks collection --help
```
Help output:
```
codehooks collection [project] [output]
Show collections for space
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to query
-s, --space Select which space to query
--json JSON output format
--sys Show system collections [boolean]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### createcollection
```sh
$ codehooks createcollection --help
```
Help output:
```
codehooks createcollection [collection]
Create a new collection
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
--collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
--cap Cap collection to max documents
--capdelay Delay capping to millis
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks createcollection pets
codehooks createcollection logs --cap 5000
```
### dropcollection
```sh
$ codehooks dropcollection --help
```
Help output:
```
codehooks dropcollection [collection]
Delete all data in collection and remove collection name
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
-c, --collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks dropcollection pets
```
### add-schema
```sh
$ codehooks add-schema --help
```
Help output:
```
codehooks add-schema [collection] [schema]
Add a JSON schema to a collection
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
--collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
--schema Path to file with JSON schema for collection [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks add-schema --collection 'person' --schema './personSchema.json'
```
### remove-schema
```sh
$ codehooks remove-schema --help
```
Help output:
```
codehooks remove-schema [collection]
Remove JSON schema for a collection
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
--collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks remove-schema --collection 'person'
```
### cap-collection
```sh
$ codehooks cap-collection --help
```
Help output:
```
codehooks cap-collection [collection] [cap] [capdelay]
Cap a collection
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
--collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
--cap Cap collection to max documents [default: 1000]
--capdelay Delay capping to max millis [default: 1000]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks capcollection --collection 'temperature' --cap 10000
```
### uncap-collection
```sh
$ codehooks uncap-collection --help
```
Help output:
```
codehooks uncap-collection [collection]
Remove cap on a collection
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
--collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks uncap-collection --collection 'temperature'
```
### import
```sh
$ codehooks import --help
```
Help output:
```
codehooks import [filepath] [collection] [project] [space] [dryrun]
Import JSON or CSV data from file
Options:
-d, --debug show debug (verbose) information
-p, --project Project name [string] [required]
-s, --space A space in your project [string]
-c, --collection A collection in a Space [string] [required]
-f, --filepath File to import [string] [required]
--separator Field separator character, default is ',', also normal with '\t' or ';' [string] [default: ","]
--dryrun Test only, will not import any data [boolean] [default: false]
--rowcount Add a row count field to each imported record [boolean] [default: false]
--admintoken Use admin token authentication (use for CI)
--local Import data to local development server on port parameter [number]
--encoding String encoding to use, latin1, utf8, ascii, hex, ucs2 [string] [default: "utf8"]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
# shorthand
codehooks import ./myfile.csv mycollection
# explicit parameters
codehooks import --filepath ./myfile.csv --collection mycollection
# parameter shortcuts
codehooks import -f ./myfile.json -c mycollection
# Import CSV data with custom separator and encoding
codehooks import -f ./myfile.csv -c mycollection --separator ';' --encoding 'latin1'
```
### export
```sh
$ codehooks export --help
```
Help output:
```
codehooks export [collection] [project] [space]
Export JSON or CSV data
Options:
-d, --debug show debug (verbose) information
-p, --project Project name [string] [required]
-s, --space a space name in your project [string]
--collection A collection in the space [string] [required]
-f, --filepath Filename to save export data [string]
--csv Export to CSV [boolean]
--jsonl Export to JSONL (JSON Lines) [boolean]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### add-token
```sh
$ codehooks add-token --help
```
Help output:
```
codehooks add-token [readonly]
Add token to space
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
--readonly [boolean] [default: false]
--description [string] [default: ""]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### remove-token
```sh
$ codehooks remove-token --help
```
Help output:
```
codehooks remove-token [token]
Remove token from space
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
-t, --token [string] [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### set-env
```sh
$ codehooks set-env --help
```
Help output:
```
codehooks set-env [key] [value] [encrypted]
Set environment variable for space
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
--encrypted [boolean] [default: false]
--key [string] [required]
--value [string] [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### remove-env
```sh
$ codehooks remove-env --help
```
Help output:
```
codehooks remove-env [key]
Remove environment variable from space
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
-k, --key [string] [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### jwks
```sh
$ codehooks jwks --help
```
Help output:
```
codehooks jwks [url]
Set/replace JWKS endpoint for OAuth2 authentication. Set to "" (empty string) to remove.
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
--url URL of JWKS endpoint (must be https) [string] [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### whitelist
```sh
$ codehooks whitelist --help
```
Help output:
```
codehooks whitelist [ip]
Add host to whitelist (use to restrict space access)
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
--ip IP address which should be allowed access to space [string] [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### whitelist-remove
```sh
$ codehooks whitelist-remove --help
```
Help output:
```
codehooks whitelist-remove [ip]
Remove host from whitelist
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
--ip The IP address to remove from the whitelist. [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### completion
```sh
$ codehooks completion --help
```
Help output:
```
codehooks completion
Generate command completion script. Just add this to your .bashrc, .bash_profile, .zshrc (or similar) on *nix machines
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### remove-project
```sh
$ codehooks remove-project --help
```
Help output:
```
codehooks remove-project [projectname]
Remove the project
Options:
-d, --debug show debug (verbose) information
-p, --projectname Project name [string] [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### remove-space
```sh
$ codehooks remove-space --help
```
Help output:
```
codehooks remove-space [space] [projectname]
Remove space and data
Options:
-d, --debug show debug (verbose) information
--projectname Project name
--space A space in your project [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### admintokens
```sh
$ codehooks admintokens --help
```
Help output:
```
codehooks admintokens
Show active admintokens for account or team
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### add-admintoken
```sh
$ codehooks add-admintoken --help
```
Help output:
```
codehooks add-admintoken
Add admin token to account or team (for use with CI)
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### remove-admintoken
```sh
$ codehooks remove-admintoken --help
```
Help output:
```
codehooks remove-admintoken
Remove admin token from account or team
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
## License
MIT.
---
## Client code examples
*(Note: This content contains MDX/JSX code)*
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
Client applications communicate with a Codehooks API and database via a secure REST API.
You can interface with Codehooks from most popular programming languages and platforms, such as: cURL, JavaScript, Python, PHP, Java, C#, Kotlin, R and Swift.
In the code examples shown below we use `mystore-fafb` as an example Codehooks project name and `dev` as a project data space. Replace this with your own project name, and use a valid API token (`x-apikey`) or a JWT/JWKS integration (`Bearer `).
## Code examples for popular programming languages
`GET` documents from the salesorder collection.
```bash
curl --location 'https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
`POST` a new document to the salesorder collection.
```bash
curl --location 'https://mystore-fafb.api.codehooks.io/dev/salesorder' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149' \
--header 'Content-Type: application/json' \
--data '{
"customerID": 1234,
"productID": 21435465,
"quantity": 3,
"payment": "subscription"
}'
```
`PATCH` (update) an existing document in the salesorder collection.
```bash
curl --location --request PATCH 'https://mystore-fafb.api.codehooks.io/dev/salesorder/9876' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149' \
--header 'Content-Type: application/json' \
--data '{
"quantity": 2,
"payment": "card"
}'
```
`DELETE` a document in the salesorder collection.
```bash
curl --location --request DELETE 'https://mystore-fafb.api.codehooks.io/dev/salesorder/9876' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
`GET` documents from the salesorder collection.
```js
var myHeaders = new Headers();
myHeaders.append('x-apikey', '3c932310-3fab-4ba3-8102-b75ba0f05149');
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow',
};
fetch(
'https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234',
requestOptions
)
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log('error', error));
```
`POST` a new document to the salesorder collection.
```js
var myHeaders = new Headers();
myHeaders.append('x-apikey', '3c932310-3fab-4ba3-8102-b75ba0f05149');
myHeaders.append('Content-Type', 'application/json');
var raw = JSON.stringify({
customerID: 1234,
productID: 21435465,
quantity: 3,
payment: 'subscription',
});
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow',
};
fetch('https://mystore-fafb.api.codehooks.io/dev/salesorder', requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.log('error', error));
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```js
var myHeaders = new Headers();
myHeaders.append('x-apikey', '3c932310-3fab-4ba3-8102-b75ba0f05149');
myHeaders.append('Content-Type', 'application/json');
var raw = JSON.stringify({
quantity: 2,
payment: 'card',
});
var requestOptions = {
method: 'PATCH',
headers: myHeaders,
body: raw,
redirect: 'follow',
};
fetch(
'https://mystore-fafb.api.codehooks.io/dev/salesorder/9876',
requestOptions
)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.log('error', error));
```
`DELETE` a document in the salesorder collection.
```js
var myHeaders = new Headers();
myHeaders.append('x-apikey', '3c932310-3fab-4ba3-8102-b75ba0f05149');
var requestOptions = {
method: 'DELETE',
headers: myHeaders,
redirect: 'follow',
};
fetch(
'https://mystore-fafb.api.codehooks.io/dev/salesorder/9876',
requestOptions
)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.log('error', error));
```
`GET` documents from the salesorder collection.
```py title="Using http.client"
import requests
url = "https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234"
payload = {}
headers = {
'x-apikey': '3c932310-3fab-4ba3-8102-b75ba0f05149'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
```
`POST` a new document to the salesorder collection.
```py
import http.client
import json
conn = http.client.HTTPSConnection("mystore-fafb.api.codehooks.io")
payload = json.dumps({
"customerID": 1234,
"productID": 21435465,
"quantity": 3,
"payment": "subscription"
})
headers = {
'x-apikey': '3c932310-3fab-4ba3-8102-b75ba0f05149',
'Content-Type': 'application/json'
}
conn.request("POST", "/dev/salesorder", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```py
import http.client
import json
conn = http.client.HTTPSConnection("mystore-fafb.api.codehooks.io")
payload = json.dumps({
"quantity": 2,
"payment": "card"
})
headers = {
'x-apikey': '3c932310-3fab-4ba3-8102-b75ba0f05149',
'Content-Type': 'application/json'
}
conn.request("PATCH", "/dev/salesorder/9876", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
```
`DELETE` a document in the salesorder collection.
```py
import http.client
conn = http.client.HTTPSConnection("mystore-fafb.api.codehooks.io")
payload = ''
headers = {
'x-apikey': '3c932310-3fab-4ba3-8102-b75ba0f05149'
}
conn.request("DELETE", "/dev/salesorder/9876", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
```
`GET` documents from the salesorder collection.
```cs title="Using HttpClient"
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234");
request.Headers.Add("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149");
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
```
`POST` (create) a new document to the salesorder collection.
```cs
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://mystore-fafb.api.codehooks.io/dev/salesorder");
request.Headers.Add("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149");
var content = new StringContent("{\n \"customerID\": 1234,\n \"productID\": 21435465,\n \"quantity\": 3,\n \"payment\": \"subscription\"\n}", null, "application/json");
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```cs
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Patch, "https://mystore-fafb.api.codehooks.io/dev/salesorder/9876");
request.Headers.Add("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149");
var content = new StringContent("{\n \"quantity\": 2,\n \"payment\": \"card\"\n}", null, "application/json");
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
```
`DELETE` a document in the salesorder collection.
```cs
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Delete, "https://mystore-fafb.api.codehooks.io/dev/salesorder/9876");
request.Headers.Add("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149");
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
```
`GET` documents from the salesorder collection.
```php title="Using Guzzle"
'3c932310-3fab-4ba3-8102-b75ba0f05149'
];
$request = new Request('GET', 'https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234', $headers);
$res = $client->sendAsync($request)->wait();
echo $res->getBody();
```
`POST` (create) a new document to the salesorder collection.
```php
'3c932310-3fab-4ba3-8102-b75ba0f05149',
'Content-Type' => 'application/json'
];
$body = '{
"customerID": 1234,
"productID": 21435465,
"quantity": 3,
"payment": "subscription"
}';
$request = new Request('POST', 'https://mystore-fafb.api.codehooks.io/dev/salesorder', $headers, $body);
$res = $client->sendAsync($request)->wait();
echo $res->getBody();
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```php
'3c932310-3fab-4ba3-8102-b75ba0f05149',
'Content-Type' => 'application/json'
];
$body = '{
"quantity": 2,
"payment": "card"
}';
$request = new Request('PATCH', 'https://mystore-fafb.api.codehooks.io/dev/salesorder/9876', $headers, $body);
$res = $client->sendAsync($request)->wait();
echo $res->getBody();
```
`DELETE` a document in the salesorder collection.
```php
'3c932310-3fab-4ba3-8102-b75ba0f05149'
];
$request = new Request('DELETE', 'https://mystore-fafb.api.codehooks.io/dev/salesorder/9876', $headers);
$res = $client->sendAsync($request)->wait();
echo $res->getBody();
```
`GET` documents from the salesorder collection.
```java title="Using Unirest"
Unirest.setTimeouts(0, 0);
HttpResponse response = Unirest.get("https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234")
.header("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.asString();
```
`POST` (create) a new document to the salesorder collection.
```java
Unirest.setTimeouts(0, 0);
HttpResponse response = Unirest.post("https://mystore-fafb.api.codehooks.io/dev/salesorder")
.header("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.header("Content-Type", "application/json")
.body("{\n \"customerID\": 1234,\n \"productID\": 21435465,\n \"quantity\": 3,\n \"payment\": \"subscription\"\n}")
.asString();
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```java
Unirest.setTimeouts(0, 0);
HttpResponse response = Unirest.patch("https://mystore-fafb.api.codehooks.io/dev/salesorder/9876")
.header("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.header("Content-Type", "application/json")
.body("{\n \"quantity\": 2,\n \"payment\": \"card\"\n}")
.asString();
```
`DELETE` a document in the salesorder collection.
```java
Unirest.setTimeouts(0, 0);
HttpResponse response = Unirest.delete("https://mystore-fafb.api.codehooks.io/dev/salesorder/9876")
.header("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.asString();
```
`GET` documents from the salesorder collection.
```swift title="Using URLSession"
var request = URLRequest(url: URL(string: "https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234")!,timeoutInterval: Double.infinity)
request.addValue("3c932310-3fab-4ba3-8102-b75ba0f05149", forHTTPHeaderField: "x-apikey")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
print(String(data: data, encoding: .utf8)!)
}
task.resume()
```
`POST` (create) a new document to the salesorder collection.
```swift
let parameters = "{\n \"customerID\": 1234,\n \"productID\": 21435465,\n \"quantity\": 3,\n \"payment\": \"subscription\"\n}"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://mystore-fafb.api.codehooks.io/dev/salesorder")!,timeoutInterval: Double.infinity)
request.addValue("3c932310-3fab-4ba3-8102-b75ba0f05149", forHTTPHeaderField: "x-apikey")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = postData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
print(String(data: data, encoding: .utf8)!)
}
task.resume()
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```swift
let parameters = "{\n \"quantity\": 2,\n \"payment\": \"card\"\n}"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://mystore-fafb.api.codehooks.io/dev/salesorder/9876")!,timeoutInterval: Double.infinity)
request.addValue("3c932310-3fab-4ba3-8102-b75ba0f05149", forHTTPHeaderField: "x-apikey")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "PATCH"
request.httpBody = postData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
print(String(data: data, encoding: .utf8)!)
}
task.resume()
```
`DELETE` a document in the salesorder collection.
```swift
var request = URLRequest(url: URL(string: "https://mystore-fafb.api.codehooks.io/dev/salesorder/9876")!,timeoutInterval: Double.infinity)
request.addValue("3c932310-3fab-4ba3-8102-b75ba0f05149", forHTTPHeaderField: "x-apikey")
request.httpMethod = "DELETE"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
print(String(data: data, encoding: .utf8)!)
}
task.resume()
```
`GET` documents from the salesorder collection.
```kotlin title="Using Okhttp"
val client = OkHttpClient()
val request = Request.Builder()
.url("https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234")
.addHeader("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.build()
val response = client.newCall(request).execute()
```
`POST` (create) a new document to the salesorder collection.
```kotlin
val client = OkHttpClient()
val mediaType = "application/json".toMediaType()
val body = "{\n \"customerID\": 1234,\n \"productID\": 21435465,\n \"quantity\": 3,\n \"payment\": \"subscription\"\n}".toRequestBody(mediaType)
val request = Request.Builder()
.url("https://mystore-fafb.api.codehooks.io/dev/salesorder")
.post(body)
.addHeader("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.addHeader("Content-Type", "application/json")
.build()
val response = client.newCall(request).execute()
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```kotlin
val client = OkHttpClient()
val mediaType = "application/json".toMediaType()
val body = "{\n \"quantity\": 2,\n \"payment\": \"card\"\n}".toRequestBody(mediaType)
val request = Request.Builder()
.url("https://mystore-fafb.api.codehooks.io/dev/salesorder/9876")
.patch(body)
.addHeader("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.addHeader("Content-Type", "application/json")
.build()
val response = client.newCall(request).execute()
```
`DELETE` a document in the salesorder collection.
```kotlin
val client = OkHttpClient()
val mediaType = "text/plain".toMediaType()
val body = "".toRequestBody(mediaType)
val request = Request.Builder()
.url("https://mystore-fafb.api.codehooks.io/dev/salesorder/9876")
.method("DELETE", body)
.addHeader("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.build()
val response = client.newCall(request).execute()
```
`GET` documents from the salesorder collection.
```r title="Using RCurl"
library(RCurl)
headers = c(
"x-apikey" = "3c932310-3fab-4ba3-8102-b75ba0f05149"
)
res <- getURL("https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234", .opts=list(httpheader = headers, followlocation = TRUE))
cat(res)
```
`POST` (create) a new document to the salesorder collection.
```r
library(RCurl)
headers = c(
"x-apikey" = "3c932310-3fab-4ba3-8102-b75ba0f05149",
"Content-Type" = "application/json"
)
params = "{
\"customerID\": 1234,
\"productID\": 21435465,
\"quantity\": 3,
\"payment\": \"subscription\"
}"
res <- postForm("https://mystore-fafb.api.codehooks.io/dev/salesorder", .opts=list(postfields = params, httpheader = headers, followlocation = TRUE), style = "httppost")
cat(res)
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```r
library(RCurl)
headers = c(
"x-apikey" = "3c932310-3fab-4ba3-8102-b75ba0f05149",
"Content-Type" = "application/json"
)
params = "{
\"quantity\": 2,
\"payment\": \"card\"
}"
res <- getURLContent("https://mystore-fafb.api.codehooks.io/dev/salesorder/9876", customrequest = "PATCH", postfields = params, httpheader = headers, followlocation = TRUE)
cat(res)
```
`DELETE` a document in the salesorder collection.
```r
library(RCurl)
headers = c(
"x-apikey" = "3c932310-3fab-4ba3-8102-b75ba0f05149"
)
res <- httpDELETE("https://mystore-fafb.api.codehooks.io/dev/salesorder/9876", httpheader = headers, followlocation = TRUE)
cat(res)
```
## Links for further reading
- [Database REST API](/docs/database-rest-api)
- [Database query language](/docs/nosql-database-query-language)
- [Serverless function NoSQL Database API](/docs/nosql-database-api)
---
## Concepts overview
*(Note: This content contains MDX/JSX code)*
Codehooks.io simplifies and speed up system integration and API development by combining serverless JavaScript functions and fast Database persistence with a coherent and well documented API. Applications are easily deployed to the secure and scalable Codehook managed cloud.
Forget about cluttering servers, databases, infrastrucure and protocols - just focus on what matters.
Codehooks includes these main features:
- **Studio** - Developer friendly web based tools for managing both data and code
- **Node.js and JavaScript** - Same language for frontend and backend development (ES6 and Typescript)
- **NPM** - Node.js package manager (npm) integration
- **Node.js Express** - API development with Express-like API
- **NoSQL** - Document database with Mongodb-like API
- **Key-Value** - Key-Value database with a Redis-like API (subset)
- **Worker Queues** - Persistent queues with worker functions
- **Background jobs** - Cron jobs with scheduled worker functions
- **CLI** - Powerful Command Line Interface (CLI) for productivity and DevOps/CI support
Read on to learn more about how development with Codehooks.io can simplify and add value to your project and team.
> Unless you've already have, please read the [quick start](/docs) first to learn how to install the CLI, and how to sign up/login to create your first Codehooks project.
## Codehooks main concepts
### Project
A project is the top level concept in Codehooks. A project is owned by an account (private or company) and contains one or multiple "spaces" with your deployed code, data and access credentials in an isolated and secure unit. You can create multiple projects within your account and you can join/invite others to a project. An example project structure is shown below:
- jane@example.com (private or corporate account owner)
- **customers** (project name)
- **dev** (development space)
- Database instance
- Security settings (API tokens, environment secrets, IP filters ...)
- Source code (deployed version 1.1.12)
- Developers (joe@example.com, pete@example.com)
- **prod** (production space)
- Database instance
- Security settings (API tokens, environment secrets, IP filters ...)
- Source code (deployed version 1.1.0)
- Developers (joe@example.com, pete@example.com)
- **salesdb** (project name for another project)
- ...
Projects are created with the CLI command `coho create` ([CLI docs here](cli#create))
:::info note
You can also manage your projects and spaces using the account user interface at [https://account.codehooks.io](https://account.codehooks.io).
:::
### The project application source code
A Codehooks application follows the familiar principles and best practices of modern JavaScript development. A project directory typically contains:
- An `index.js` file for the application main entry code
- A `package.json` file with library dependencies
- A `config.json` file with project information
We also recommend that you add source control (GIT) to manage your versions and branches.
Typically this will be a git repo with branches that are deployed to various spaces.
### A minimal example
The following example shows a complete process for creating a new project with an application that is deployed to the Codehooks serverless cloud.
First, create and setup a new project application:
```bash
coho create example
cd example
npm init --yes
npm install codehooks-js --save
```
After running the commands your project directory contains these files:
```bash
.
βββ config.json
βββ index.js
βββ node_modules
βββ package-lock.json
βββ package.json
```
If you inspect the source file index.js you'll see the default generated application code:
```js
/*
* Auto generated Codehooks (c) example
*/
import { app, crudlify } from 'codehooks-js';
// test route for https://.api.codehooks.io/dev/
app.get('/', (req, res) => {
res.send('CRUD server ready');
});
// Use Crudlify to create a REST API for any collection
crudlify(app);
// bind to serverless runtime
export default app.init();
```
We can now deploy the application (in our case example-4g9p) to the cloud with the CLI command `coho deploy` ([CLI docs here](cli#deploy)).
```bash
coho deploy
# Server output example
Project: example-4g9p Space: dev
Deployed Codehook successfully! π
```
After deployment we can inspect the application details with the `coho info` command, in this example showing our default space `dev` with its base endpoint URL and access token(s):
```bash
coho info --examples
Project name: example-4g9p
Team: YOUR-NAME (personal account)
API endpoint: https://example-4g9p.api.codehooks.io/dev/*
Spaces:
ββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ¬βββββββ¬βββββββ
β Name β Tokens β Jwks β Env β
ββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌβββββββΌβββββββ€
β dev (active) β a77926ca-xxx-yyyy-zzzzz-14eee564f8d5 (RW) β β β
ββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββ΄βββββββ΄βββββββ
```
After successful deployment we can test our application endpoint with curl. This minimal example and test shows that our application is deployed to a secure endpoint and returns the expected result:
```bash
curl -X GET 'https://example-4g9p.api.codehooks.io/dev' \
-H 'x-apikey: a77926ca-xxx-yyyy-zzzzz-14eee564f8d5'
# API output example from server
CRUD server ready
```
### Project spaces
A project space is a self-contained and isolated bundle of code, datastores and security settings within a specific project. All projects have a default space called `dev` which is created automatically when you add a project. You create new spaces with the `coho add` command ([CLI docs here](cli#add)). You can easily switch between different project spaces with the `coho use` command ([CLI docs here](cli#use)). Spaces can also have restricted access (team ADMINs only), which is convenient for production deployments.
For example, in your project you want to have isolated development, testing and production spaces.
```
Project-X
βββ dev
Β Β βββ source code (git branch dev)
Β Β βββ security settings & env variables
Β Β βββ data stores
βββ test
Β Β βββ source code (git branch test)
Β Β βββ security settings & env variables
Β Β βββ data stores
βββ prod (restricted)
Β Β βββ source code (git branch prod)
Β Β βββ security settings & env variables
Β Β βββ data stores
```
### Deploy your application to the Codehooks serverless cloud
Each project space is created as an isolated resource group in the Codehooks cloud.
For example, the `coho add prod` command creates a project space called `prod`. By switching to the space with the `coho use prod` command, this space is now the active (changed in the config.json project file).
On deployment with the `coho deploy` command, the current version of your application source code is packed and distributed as a serverless runtime to the **prod** space in the Codehook cloud.
Similarily, switching to another space, e.g. by using the `coho use dev`, sets the **dev** space as the active, and `coho deploy` will deploy the application to the active **dev** space.
This is a powerful feature, which effectively enables you to run multiple versions of your application with separate settings, environments and data stores.
### Built in Datastores
Persisting, querying and manipulating data is essential in any application. Codehooks comes with a built in datastore that supports **streaming** NoSQL and Key-Value operations. No need to think about drivers and protocols, it's built directly into the Codehooks APIs.
- NoSQL object data are accessed with the standard [Database API](nosql-database-api).
- Key-Value data are accessed with the standard [Key-Value API](key-value-database-api)
#### Data wrangling
Using the CLI command `coho import`, you can easily import any CSV or JSON file into a datastore.
Furthermore, the `coho query` command lets you query and transform data in advanced ways.
```bash title="Example: import and query of Stock data"
coho import -c stocks -f ~/Downloads/all_stocks_5yr.csv
coho query stocks --query 'Name=GOOG&open>10' --limit 2 --table
βββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ
β date β open β high β low β close β volume β Name β _id β
βββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββ€
β 2013-03-β¦ β 15.98 β 16.36 β 15.93 β 16.25 β 8383300 β GOOG β 18007b5fβ¦ β
βββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββ€
β 2013-03-β¦ β 14.7 β 14.93 β 14.5 β 14.82 β 9125300 β GOOG β 18007b5fβ¦ β
βββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ
```
### Fast development flow
The development process with Codehooks is fast.
You use your favorite tools (e.g. VS Code) combined with the CLI.
For example, after creating a new project and a project space, you write some code that you need to verify, test, debug and deploy. The following steps shows a typical development flow.
1. Open the index.js and other source files in your favorite code editor
2. Set the active space to deploy your application, e.g. `coho use dev`
3. Deploy the application with `coho deploy`
4. Check logs to see what's going on in your deployed application, `coho log --follow`
5. Find and fix bugs in your code, and redeploy with `coho deploy`
### Command Line Interface (CLI)
The Codehooks CLI is a key tool for managing your account, projects and applications. The CLI has all the commands and functionality you need to manage projects, spaces, security and your serverless JavaScript functions.
Codehooks has a complete set of features for teams to collaborate.
The [account UI](https://account.codehooks.io) lets you create teams and invite team members.
Read more about the CLI and the full set of commands and features [here](cli).
:::info note
You can also manage your projects and spaces using the account user interface at [https://account.codehooks.io](https://account.codehooks.io).
:::
---
## REST API query and Database CRUD API
*(Note: This content contains MDX/JSX code)*
A new Codehooks.io application has a complete and secure REST API for basic database CRUD (Create, Read, Update, Delete) operations using REST API queries. The CRUD REST API is implemented by bundling the deployed application with the NPM package [codehooks-js](https://www.npmjs.com/package/codehooks-js) and the [crudlify API](/docs/appeventapi#appcrudlifyschema-options).
When you create a new project a default auto generated application is deployed, this application implements a full database CRUD REST API with built-in NoSQL query functionality. The code for this is shown below.
```js
import { app } from 'codehooks-js';
app.crudlify(); // REST CRUD API
export default app.init();
```
All database URL endpoints are prefixed with the project ID, Codehooks API endpoint, and the database space name. For example, if your project ID is `myproject` and the database space is `dev`, then the full URL endpoint for the REST API is: `https://myproject-ff00.api.codehooks.io/dev/people?name=Ally`.
```mermaid
graph TD
A[REST API] --> B[CRUD Operations]
B --> C[Create=POST]
B --> D[Read/query=GET]
B --> E[Update=PUT/PATCH]
B --> F[Delete=DELETE]
C --> G[(Database)]
D --> G[(Database)]
E --> G[(Database)]
F --> G[(Database)]
accTitle: Database REST API query and CRUD
accDescr: You use the REST API to update the database using CRUD operations and REST API query
```
## Overview of Database REST API operations and queries
| API description | HTTP verb | Route |
| :------------------------------------------------------------------------ | --------- | --------------------------- |
| [Get list of documents](#get-list-of-documents) | `GET` | `/:collection` |
| [Get documents by query](#get-documents-by-query) | `GET` | `/:collection?query` |
| [Get document by ID](#get-document-by-id) | `GET` | `/:collection/:ID` |
| [Create a new document](#create-a-new-document) | `POST` | `/:collection` |
| [Update a document by ID](#update-a-document-by-id) | `PATCH` | `/:collection/:ID` |
| [Replace a document by ID](#replace-a-document-by-id) | `PUT` | `/:collection/:ID` |
| [Delete a document by ID](#delete-a-document-by-id) | `DELETE` | `/:collection/:ID` |
| [Update multiple documents by query](#update-multiple-documents-by-query) | `PATCH` | `/:collection/:ID/_byquery` |
| [Delete multiple documents by query](#delete-multiple-documents-by-query) | `DELETE` | `/:collection/:ID/_byquery` |
:::note Database collections
Create and manage collections with the Codehooks CLI command [`coho createcollection`](/docs/cli#createcollection) or using the Codehooks Studio [β Create collection] menu.
:::
:::note Security
All databases are protected with encrypted HTTPS protocol, secure API tokens or JWT tokens. You can also add IP filters and much more, read more about [Authentication here](/docs/authentication).
**API tokens**: List your database API tokens with the CLI command [`coho info`](/docs/cli.md#info). Create new API tokens with the CLI command [`coho add-token`](/docs/cli.md#add-token).
:::
## Database REST API
### Get list of documents
Retrieve all collection data.
**URL:** `/:collection[?options]`
**Method:** `GET`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
**Options:**
- **limit**: limit result, e.g. `?limit=2`
- **offset**: skip forward in data result set
- **fields**: comma separated list of fields to show, e.g. `?limit=2&fields=Last Name,Sex`
- **sort**: comma separated list of fields to sort result by, e.g. `?limit=2&sort=-Sex,Last Name`
**Returns** Array of JSON documents
**Code example**
```bash
curl 'https://myproject-ff00.api.codehooks.io/dev/people?limit=2' \
-H 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
**Success response** `200 OK`
```json
[
{
"Index": 54901,
"User Id": "d088FCEC6EDEF20",
"First Name": "Michaela",
"Last Name": "Callahan",
"Sex": "Male",
"Email": "yesenia31@example.net",
"Phone": "108-950-4850x6836",
"Date of birth": "2010-11-07",
"Job Title": "Research scientist (maths)",
"_id": "64bb88b51ed9a3057ac99d9c"
},
{
"Index": 54902,
"User Id": "40Fd72E5AaC5b67",
"First Name": "Lacey",
"Last Name": "Saunders",
"Sex": "Male",
"Email": "vcarey@example.net",
"Phone": "623.506.2528x932",
"Date of birth": "1946-06-28",
"Job Title": "Insurance claims handler",
"_id": "64bb88b51ed9a3057ac99d9d"
}
]
```
**Error response** `401 no access`
```html
401 no access
```
### REST API query - get documents by query
Retrieve collection data filtered by a query parameter.
:::tip
For a complete overview of the NoSQL query language and how to perform the REST API queries, check out the [NoSQL and REST API Query language](nosql-database-query-language) documentation.
:::
**URL:** `/:collection[?query&options]`
**Method:** `GET`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
**Query:**
- Simple REST API queries using URL parameters:
- **name=value** - e.g. `?First Name=Michaela`
- Advanced MongoDB query using URL JSON:
- **q=\{\.\.\.\}** - e.g. `?q={"First Name": {"$regex": "en"}, "Last Name": {"$in": ["Saunders", "Massey"]}}`
**Options:**
- **limit**: limit result, e.g. `?limit=2`
- **offset**: skip forward in data result set
- **fields**: comma separated list of fields to show, e.g. `?limit=2&fields=Last Name,Sex`
- **sort**: comma separated list of fields to sort result by, e.g. `?limit=2&sort=-Sex,Last Name`
**Returns** Array of JSON documents
**Code example:** simple query
```bash
curl --location 'https://myproject-ff00.api.codehooks.io/dev/people?First%20Name=Michaela&fields=First%20Name%2CEmail%2C_id' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
**Success response** `200 OK`
```json
[
{
"First Name": "Michaela",
"Email": "yesenia31@example.net",
"_id": "64bb88b51ed9a3057ac99d9c"
}
]
```
**Code example:** advanced query
:::tip URL encoding
Notice that advanced query parameter `q={...}` should be programmatically URL encoded before sent to the server.
E.g. the query:
```js
let query = {
"First Name": { "$regex": "en" },
"Last Name": { "$in": ["Saunders", "Massey"] }
}
```
For example, you can use JavaScript's `query = encodeURIComponent(JSON.stringify(query))`:
Then the encoded query equals:
```html
{%22First%20Name%22%3A{%22%24regex%22%3A%22en%22}%2C%22Last%20Name%22%3A{%22%24in%22%3A[%22Saunders%22%2C%22Massey%22]}}
```
:::
```bash
curl --location --globoff 'https://myproject-ff00.api.codehooks.io/dev/people?q={%22First%20Name%22%3A{%22%24regex%22%3A%22en%22}%2C%22Last%20Name%22%3A{%22%24in%22%3A[%22Saunders%22%2C%22Massey%22]}}' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
**Success response** `200 OK`
```json
[
{
"Index": 54907,
"User Id": "18E57F5a6aaC1ab",
"First Name": "Darlene",
"Last Name": "Saunders",
"Sex": "Female",
"Email": "noah55@example.net",
"Phone": "001-158-700-3226",
"Date of birth": "1970-08-23",
"Job Title": "Scientist, forensic",
"_id": "64bb88b51ed9a3057ac99da2"
},
{
"Index": 54933,
"User Id": "aeD915eA429Fb02",
"First Name": "Lauren",
"Last Name": "Massey",
"Sex": "Male",
"Email": "stuart55@example.net",
"Phone": "+1-716-581-5746x2442",
"Date of birth": "2001-10-21",
"Job Title": "Information systems manager",
"_id": "64bb88b51ed9a3057ac99dbc"
}
]
```
**Error response** `401 no access`
```html
401 no access
```
**Error response** `400 Bad Request`
```html
Unexpected token } in JSON at position 5
```
### Get document by ID
Retrieve a document from a collection.
**URL:** `/:collection/:ID`
**Method:** `GET`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
- **ID**: an existing document `_id` value
**Returns** A JSON document
**Code example**
```bash
curl --location 'https://myproject-ff00.api.codehooks.io/dev/people/64bb88b51ed9a3057ac99d9c' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
**Success response** `200 OK`
```json
{
"Index": 54901,
"User Id": "d088FCEC6EDEF20",
"First Name": "Michaela",
"Last Name": "Callahan",
"Sex": "Male",
"Email": "yesenia31@example.net",
"Phone": "108-950-4850x6836",
"Date of birth": "2010-11-07",
"Job Title": "Research scientist (maths)",
"_id": "64bb88b51ed9a3057ac99d9c"
}
```
**Error response** `401 no access`
```
401 no access
```
**Error response** `404 Not Found`
```
3 INVALID_ARGUMENT: NotFoundError: Key not found in database [64bb88b51ed9a3057ac99d9cc]
```
### Create a new document
Insert a new JSON document in a collection.
**URL:** `/:collection`
**Method:** `POST`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
- **body**: a valid JSON document
**Returns** The created JSON document
**Code example**
```bash
curl --location 'https://myproject-ff00.api.codehooks.io/dev/people' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149' \
--header 'Content-Type: application/json' \
--data-raw '{
"First Name": "Jim",
"Last Name": "Callahan",
"Email": "jim@example.net",
"Phone": "108-950-4850x6836",
"Date of birth": "1995-11-07",
"Job Title": "Software tester"
}'
```
**Success response** `201 Created`
```js
{
"First Name": "Jim",
"Last Name": "Callahan",
"Email": "jim@example.net",
"Phone": "108-950-4850x6836",
"Date of birth": "1995-11-07",
"Job Title": "Software tester",
"_id": "64bbc861f13609074a5d981a"
}
```
**Error response** `401 no access`
```
401 no access
```
**Error response** `400 Bad Request`
E.g. error when POST an invalid JSON document or a JSON-schema `schemaError` error occurs.
```
{
"schemaError": [
{
"instancePath": "",
"schemaPath": "#/required",
"keyword": "required",
"params": {
"missingProperty": "firstName"
},
"message": "must have required property 'firstName'"
}
]
}
```
### Update a document by ID
Update a JSON document in a collection.
**URL:** `/:collection/:ID`
**Method:** `PATCH`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
- **ID**: a valid document `_id`
- **body**: a valid JSON document
**Returns** The updated JSON document
**Code example**
```bash
curl --location --request PATCH 'https://myproject-ff00.api.codehooks.io/dev/people/64bbc861f13609074a5d981a' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149' \
--header 'Content-Type: application/json' \
--data '{
"Phone": "123-345-12332x6836",
"Job Title": "Bug master"
}'
```
**Success response** `200 OK`
```js
{
"First Name": "Jim",
"Last Name": "Callahan",
"Email": "jim@example.net",
"Phone": "123-345-12332x6836",
"Date of birth": "1995-11-07",
"Job Title": "Bug master",
"_id": "64bbc861f13609074a5d981a"
}
```
**Error response** `401 no access`
```
401 no access
```
**Error response** `400 Bad Request`
E.g. invalid JSON document or `schemaError`.
```
{
"schemaError": [
...
]
}
```
### Replace a document by ID
Replace a JSON document in a collection.
**URL:** `/:collection/:ID`
**Method:** `PUT`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
- **ID**: a valid document `_id`
- **body**: a valid JSON document
**Returns** The replaced JSON document
**Code example**
```bash
curl --location --request PUT 'https://myproject-ff00.api.codehooks.io/dev/people/64bbc861f13609074a5d981a' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149' \
--header 'Content-Type: application/json' \
--data '{
"First Name": "Re",
"Last Name": "Placed",
"Job Title": "Replacer"
}'
```
**Success response** `200 OK`
```json
{
"First Name": "Re",
"Last Name": "Placed",
"Job Title": "Replacer",
"_id": "64bbc861f13609074a5d981a"
}
```
**Error response** `401 no access`
```
401 no access
```
E.g. invalid JSON document or `schemaError`.
```
{
"schemaError": [
...
]
}
```
### Delete a document by ID
Delete a JSON document in a collection.
**URL:** `/:collection/:ID`
**Method:** `DELETE`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
- **ID**: a valid document `_id`
**Returns** The deleted document \_id
**Code example**
```bash
curl --location --request DELETE 'https://myproject-ff00.api.codehooks.io/dev/people/64bbc861f13609074a5d981a' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
**Success response** `200 OK`
```json
{
"_id": "64bbc861f13609074a5d981a"
}
```
**Error response** `401 no access`
```
401 no access
```
### Update multiple documents by query
Update multiple JSON document by query in a collection.
**URL:** `/:collection/_byquery[?query&options]`
**Method:** `PATCH`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
- **body**: a valid JSON document to update on all matches
**Query:**
- Simple REST API queries using URL parameters:
- **name=value** - e.g. `?First Name=Michaela`
- Advanced MongoDB query using URL JSON:
- **q=\{\.\.\.\}** - e.g. `?q={"First Name": {"$regex": "en"}, "Last Name": {"$in": ["Saunders", "Massey"]}}`
**Returns** Count of updated documents
**Code example**
```bash
curl --location --request PATCH 'https://myproject-ff00.api.codehooks.io/dev/people/_byquery?Job%20Title=%22Scientist%2C%20forensic%22' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149' \
--header 'Content-Type: application/json' \
--data '{
"$set": {
"salary": "high"
},
"$inc": {
"visited": 1
}
}'
```
**Success response** `200 OK`
```json
{
"count": 2
}
```
**Error response** `401 no access`
```
401 no access
```
**Error response** `400 Bad Request`
E.g. invalid JSON document or `schemaError`.
```
{
"schemaError": [
...
]
}
```
### Delete multiple documents by query
Delete multiple JSON document by query in a collection.
:::warning Erases all matched documents by query
Please ensure that the applied query is correct. An empty query parameter will erase all documents in collection.
:::
**URL:** `/:collection/_byquery[?query&options]`
**Method:** `DELETE`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
**Query:**
- Simple REST API queries using URL parameters:
- **name=value** - e.g. `?First Name=Michaela`
- Advanced MongoDB query using URL JSON:
- **q=\{\.\.\.\}** - e.g. `?q={"First Name": {"$regex": "en"}, "Last Name": {"$in": ["Saunders", "Massey"]}}`
**Returns** Count of deleted documents
**Code example**
```bash
curl --location --request DELETE 'http://myproject-ff00.api.codehooks.io/dev/people/_byquery?Job%20Title=%22Scientist%2C%20forensic%22' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
**Success response** `200 OK`
```json
{
"count": 2
}
```
**Error response** `401 no access`
```
401 no access
```
## Data Schema support
We recommend to add JSON-schema directly to the database collection using the [CLI command](/docs/cli#add-schema) `add-schema` or the Codehooks Studio application navigating to Collection/Settings/JSON-Schema.

However, you can also take full control over the data validation in your code using these popular technologies described in the following sections.
Codehooks supports these popular data and validation schemas:
- [Yup](https://www.npmjs.com/package/yup) - Dead simple Object schema validation
- [Zod](https://www.npmjs.com/package/zod) - TypeScript-first schema validation with static type inference
- [JSON.schema](https://www.npmjs.com/package/z-schema) - Standard declarative language that allows you to annotate and validate JSON documents.
### Data validation with Yup
The code example below shows how easy it is to validate data with a [Yup](https://www.npmjs.com/package/yup) data schema.
Run `npm i yup` in project folder.
```js
import { app } from 'codehooks-js';
import { object, string, number, date } from 'yup';
// database schema
const userSchema = object({
name: string().required(),
age: number().required().positive().integer(),
email: string().email(),
website: string().url().nullable(),
createdOn: date().default(() => new Date()),
});
app.crudlify({ user: userSchema });
export default app.init(); // export app to a runtime server engine
```
### Data validation with JSON-schema
The next code example uses JSON-schema to validate data.
Run `npm i z-schema` in project folder.
```js
import { app } from 'codehooks-js';
import ZSchema from 'z-schema';
const userSchema = {
id: 'personDetails',
type: 'object',
properties: {
firstName: { type: 'string' },
lastName: { type: 'string' },
},
required: ['firstName', 'lastName'],
};
app.crudlify({ user: userSchema });
// bind to serverless runtime
export default app.init();
```
### Data validation with Zod
Zod is another popular data validation library.
Run `npm i zod` in project folder.
A simple code example using Zod is show below.
```js
import { app } from 'codehooks-js'; // Standard Codehooks.io lib
import { z } from 'zod';
const userSchema = z
.object({
username: z.string(),
email: z.string().email(),
status: z.boolean().default(true),
})
.required({ username: true, email: true });
app.crudlify({ user: userSchema });
export default app.init(); // Bind functions to the serverless cloud
```
## Database event hooks middleware
To provide additional CRUD logic, events are triggered before and after a database operation.
```js
hooks.before(, handlerFunction)
hooks.after(, handlerFunction)
```
Example event hooks are shown in the code example below.
```js
... // collapsed code
app.crudlify({user: userSchemaYup}, options).then((hooks) => {
hooks.beforePOST('user', async (data) => {
console.log("User data before saving", data)
// abort operation with a throw, cases 404 status code
// E.g. throw new Error(`BAD post for ${data}`)
// mutate data before saved to the database
data.foo = 'Was here!'
})
hooks.afterPOST('user', async (data) => {
console.log("User data after saved to the database", data)
})
})
... // collapsed code
```
## Examples
### Insert a new `user` to the database
POST a new user using curl.
```bash
curl -X POST \
'https://myproject-fef0.api.codehooks.io/dev/user' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Ally",
"email": "ally@example.com"
}'
```
### Validate data against a Yup data schema
Check that the data schema validates correctly by sending an invalid email address.
```bash
curl -X POST \
'https://myproject-fef0.api.codehooks.io/dev/user' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Sally",
"email": "sally.example.com"
}'
```
Validation error shows that Yup works.
```bash
400 Bad Request
{
"value": {
"active": true,
"email": "sally.example.com",
"name": "Sally"
},
"path": "email",
"type": "email",
"errors": [
"email must be a valid email"
],
... # chopped error message
}
```
### Run a database REST API query
```bash
curl -X GET \
'https://myproject-fef0.api.codehooks.io/dev/user?name=Ally' \
--header 'Content-Type: application/json'
```
Example output.
```json
[
{
"_id": "63fb97825f624f479034eb08",
"active": true,
"email": "ally@example.com",
"name": "Ally"
}
]
```
### Update a record in the database
```bash
curl -X PATCH \
'https://myproject-fef0.api.codehooks.io/dev/user/63fb97825f624f479034eb08' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Ally Mc. Beal"
}'
```
### Querying the database using the REST API
You can query the database REST API in two different ways.
**Simple** to use and quick queries with the URL:
```url
https://myproject-fef0.api.codehooks.io/dev/user?name=jane&age>23&limit=2&offset=5&sort=email&fields=name,age
```
Which actually produces this query object:
```js
{ name: 'Jane', age: { '$gt': 23 } }, {
fields: { name: 1, age: 1 },
sort: { email: 1 },
skip: 5,
limit: 2,
projection: { name: 1, age: 1 }
}
```
For **advanced** use, and programmatic approach, pass inn the full NoSQL/JSON REST API query and hints as URL parameters:
```url
https://myproject-fef0.api.codehooks.io/dev/user?q={"name": "Jane", "age": {"$gt": 23}}&h={"fields": { "name": 1, "age": 1 },"sort": {"email": 1 },"skip": 5,"limit": 2,"projection": { "name": 1, "age": 1 }}
```
The last option would probably use `JSON.stringify(query)` etc. to produce a valid query in the URL.
---
## Data operators
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
Data objects in a datastore can be manipulated in any update operation. To manipulate one or many data object(s) a range of special operators can be applied using the [`updateOne(...)`](nosql-database-api#updateonecollection-id-document) or [`updateMany(...)`](nosql-database-api#updatemanycollection-document-options) API.
**Operators quick overview**
## **$inc** operator
Increase or decrease a field value on an existing datastore object.
**Syntax**: `{ $inc: { : , : , ... } }`
Example:
```js
// before
{..., "followers": 42}
const operator = {$inc: {"followers": 1}}
// Call update API, e.g. updateMany or updateOne
await conn.updateMany('mycollection', {"status": "ACTIVE"}, operator)
// after
{..., "followers": 43}
```
π _Use negative values to decrement field._
## **$push** operator
Append an element to a sub array on an existing datastore object.
**Syntax**: `{ $push: { : , ... } }`
Example:
```js
// before
{"followers": 42, "comments": ["one", "three"]}
const operator = {$push: {"comments": "two"}}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{"followers": 42, "comments": ["one", "three", "two"]}
```
## **$pull** operator
Remove an element from a sub array on an existing datastore object.
Pulls all matches from array.
**Syntax**: `{ $pull: { : |, : |, ... } }`
Example:
```js
// before
{"followers": 42, "comments": ["one", "two", "three"]}
const operator = {$pull: {"comments": "two"}}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{"followers": 42, "comments": ["one", "three"]}
```
## **$pop** operator
Removes last element of a sub array on an existing datastore object.
**Syntax**: `{ $pop: { : -1 | 1, ... } }`
-1 pop first item of array, 1 pop last item of array.
Example:
```js
// before
{"followers": 42, "comments": ["one", "three", "two"]}
const operator = {$pop: {"comments": 1}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{"followers": 42, "comments": ["one", "three"]}
```
## **$addToSet** operator
Insert an unique element to a sub array on an existing datastore object.
Nothing is inserted if item already exists in set.
**Syntax**: `{ $addToSet: { : , ... } }`
Example:
```js
// before
{"followers": 42, "comments": ["one", "two", "three"]}
const operator = {$addToSet: {"items": {"name": "one", "price": 1}}}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{
"followers": 42, "comments": ["one", "two", "three"],
"items": [{"name": "one", "price": 1}]
}
```
## **$set** operator
Set any object value.
**Syntax**: `{ $set: { : , ... } }`
Example:
```js
// before
{"followers": 42, "comments": ["one", "two", "three"]}
const operator = {$set: {"address": {"street": "Main street", "ZIP": 1123}}}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{
"followers": 42, "comments": ["one", "two", "three"],
"address": {"street": "Main street", "ZIP": 1123}
}
```
## **$unset** operator
Delete an object property.
**Syntax**: `{ $unset: { : "", ... } }`
```js
// before
{"followers": 42, "comments": ["one", "two", "three"]}
const operator = {$unset: {"comments": ""}}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{
"followers": 42
}
```
## **$rename** operator
Rename an object property.
**Syntax**: `{$rename: { : , : , ... } }`
```js
// before
{"followers": 42, "comments": ["one", "two", "three"]}
const operator = {$rename: {"comments": "claps"}}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{
"followers": 42, "claps": ["one", "two", "three"]
}
```
---
## File API
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
The File API enables your application to access and manage folders and files.
The API is automatically available from the inside of any Codehook function.
**Install**
Run `npm install codehooks-js` in your project code folder.
:::info Bulk upload files
Use the CLI command [coho upload](/docs/cli#file-upload) to upload files from a local machine to the application.
:::
**API quick overview**
## readFile(path)
Read file content as text from the file system.
**Parameters**
- **path**: full path to file, e.g. '/documents/doc.html'
**Returns**: Promise with text content from file.
**Error**: throws exception if file is not found.
**Code example for reading a file**
```js {6} title="index.js"
import { app, filestore } from 'codehooks-js';
// get a file text content
app.get('/txt', async (req, res) => {
try {
const file = await filestore.readFile('/somefolder/myfile.txt');
res.set('content-type', 'text/plain');
res.end(file);
} catch (error) {
res.status(404).end('No file here');
}
});
// bind to serverless runtime
export default app.init();
```
## getReadStream(path)
Read file content as a binary stream the file system.
**Parameters**
- **path**: full path to file, e.g. `/private/dummy.pdf`
**Returns**: Promise with file stream handle.
**Error**: throws exception if file is not found.
**Code example for reading a binary file stream**
```js {6} title="index.js"
import { app, filestore } from 'codehooks-js';
// get a file binary content as stream
app.get('/pdf', async (req, res) => {
try {
const filestream = await filestore.getReadStream('/private/dummy.pdf');
res.set('content-type', 'application/pdf');
// stream content back to client
filestream
.on('data', (buf) => {
res.write(buf, 'buffer');
})
.on('end', () => {
res.end();
});
} catch (error) {
console.error(error);
res.status(404).end('No file here');
}
});
// bind to serverless runtime
export default app.init();
```
## saveFile(path, filestream)
Write a file binary stream to the file system.
**Parameters**
- **path**: full path to file, e.g. `/private/dummy.pdf`
- **filestream**: inputstream of binary buffers
**Returns**: Promise with upload result text.
**Error**: throws exception if an error occured.
**Code example for writing a binary inputstream as a new file to the file system**
```js {10} title="index.js"
import { app, filestore } from 'codehooks-js';
import { PassThrough } from 'stream';
// post a file binary content
app.post('/file', async (req, res) => {
try {
const { name } = req.query; /* e.g. /dev/file?name=/upload/dummy.pdf */
const stream = new PassThrough();
req.pipe(stream);
const result = await filestore.saveFile(name, stream);
res.end(result);
} catch (error) {
console.error(error);
res.status(404).end('Upload error');
}
});
// bind to serverless runtime
export default app.init();
```
## deleteFile(path)
Delete a file binary from the file system.
**Parameters**
- **path**: full path to file, e.g. `/private/dummy.pdf`
**Returns**: Promise with delete result text.
**Error**: throws exception if an error occured.
**Code example for deleting a file from the file system**
```js {7} title="index.js"
import { app, filestore } from 'codehooks-js';
// delete a file
app.delete('/file', async (req, res) => {
try {
const { name } = req.query;
const result = await filestore.deleteFile(name);
res.end(result);
} catch (error) {
console.error(error);
res.status(404).end('Not found');
}
});
// bind to serverless runtime
export default app.init();
```
## list(path)
List files in folders from the file system.
**Parameters**
- **path**: full path to folder, e.g. `/upload`
**Returns**: Promise with JSON array of files matching path to folder.
**Error**: throws exception if an error occured.
**Code example for listing files in a folder from the file system**
```js {6} title="index.js"
import { app, filestore } from 'codehooks-js';
// list dir
app.get('/list', async (req, res) => {
try {
const result = await filestore.list('/');
res.end(result);
} catch (error) {
console.error(error.message);
res.status(400).end('sorry list');
}
});
// bind to serverless runtime
export default app.init();
```
**Example output shows folders and files**:
```title="JSON array"
[
{"path":"/robots.txt","size":114,"date":"2023-09-29T12:12:10.000Z","_id":"6516f3b26c816e0015ca7b60"},
{"path":"/upload/dummy.pdf","size":1234,"date":"2023-09-29T16:22:10.499Z","_id":"6516f9b26c816e0015ce7b6d"},
{"path":"/upload/logo.pdf","size":259096,"date":"2023-09-30T11:48:18.003Z","_id":"6517155cd8c8a40071d6bc79"},
{"path":"/upload/report.html","size":1320,"date":"2023-09-30T12:46:31.640Z","_id":"65180b50d8fc2b01b3b2515e"}
]
```
## readFileAsBuffer(path)
Read file content into a Buffer.
**Parameters**
- **path**: full path to folder, e.g. `/upload`
**Returns**: Promise with Buffer.
**Error**: throws exception if an error occured.
**Code example for reading file content into a Buffer**
```js
// collapsed code
const buf = await filestore.readFileAsBuffer('/images/logo.png');
console.log(buf.length);
```
## Postman examples using the File API
### Upload a new PDF file

### Download a PDF file

---
## Indexing API
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
Create fast lookup indexes in a datastore. Combined with [streaming queries](nosql-database-api#getmanycollection-options), indexing can be a big improvement for your application performance.
**API quick overview**
## Datastore.open()
Datastore.open() opens a connection to a datastore in the active project space.
```js
import {Datastore} from 'codehooks-js'
async function myfunc() {
const conn = await Datastore.open();
// use conn to call API functions
...
}
```
**Returns** Promise / Datastore connection
## createIndex(collection, indexes)
Create index(es) on a collection in the current Datastore
**Parameters**
- **collection**: collection string
- **indexes**: Array of field names to index
Returns **Promise / Indexes**
**Code example**
```js
const conn = await Datastore.open();
const idx = await conn.createIndex('stocks', ['Name', 'Volume']);
```
:::warning
Index creation can take a long time on big collections.
It's better to create indexes before adding data.
:::
## removeIndex(collection, indexes)
Remove index(es) on a collection in the current Datastore
**Parameters**
- **collection**: collection string
- **indexes**: Array of field names to remove from index
Returns **Promise / Indexes**
**Code example**
```js
const conn = await Datastore.open();
const idx = await conn.removeIndex('stocks', ['Name', 'Volume']);
```
:::tip
You can also use the [CLI](cli#createindex) to manage indexes.
:::
---
## Job background workers
*(Note: This content contains MDX/JSX code)*
Job workers lets you schedule background worker functions as recurring `cron` jobs or as one-time `runAt` jobs. When a job event _triggers_, your function is called accordingly.
## Cron jobs
### Example cron jobhook
The example job worker function prints to the log each time the cron triggers.
```js
import app from 'codehooks-js';
app.job('* * * * *', minute);
app.job('*/30 * * * *', halfPast);
function minute(req, res) {
console.log('Each minute');
res.end(); // done
}
function halfPast(req, res) {
console.log('Each half hour');
res.end(); // done
}
export default app.init();
```
:::info tip
Utility to create CRON expressions [https://crontab.guru/](https://crontab.guru/)
:::
- Running jobs are stopped and re-started on each deployment (`coho deploy`)
- Running jobs are stopped on undeployment (`coho undeploy`)
### Cron Syntax
Quick reference to cron syntax.
```
βββββββββββββββ second (optional) 0-59
β βββββββββββββ minute 0-59
β β βββββββββββ hour 0-23
β β β βββββββββ day of month 1-13
β β β β βββββββ month 1-12
β β β β β βββββ day of week 0-7 (0 or 7 are sunday)
β β β β β β
β β β β β β
* * * * * *
```
### Example cron expressions
**Run five minutes after midnight, every day**
`5 0 * * *`
**Run at 2:15pm on the first of every month**
`15 14 1 * *`
**Run at 10 pm on weekdays**
`0 22 * * 1-5`
## Schedule delayed worker function dynamically with _runAt_
Cron jobs are declare at deploy-time and they run forever, or until they are changed or removed by a new deployment.
The `schedule.runAt` API is another way to create a one time job that is executed by a worker function.
### Example runAt scheduled worker function
The example worker function prints the data payload to the log each time the scheduled datetime triggers.
```js {4,17}
/*
* runAt example.
*/
import { app, schedule } from 'codehooks-js';
app.worker('myworker', (data, job) => {
const { payload } = data.body;
console.log('Delayed job', payload);
job.end();
});
// REST hook
app.get('/hello', async (req, res) => {
const when = new Date(Date.now() + 5000);
const data = { message: 'Hello later!' };
const worker = 'myworker';
await schedule.runAt(when, data, worker);
res.end();
});
// bind to serverless runtime
export default app.init();
```
If we call the `/hello` API the `coho logs` command will display the following:
```bash
dev 2023-03-23T17:37:54.198Z Delayed job {
"message": "Hello later!"
}
dev 2023-03-23T17:37:54.940Z Delayed job {
"message": "Hello later!"
}
dev 2023-03-23T17:37:55.652Z Delayed job {
"message": "Hello later!"
}
...
```
## Schedule worker function to run immediate
You can also execute a worker function without any delay like this:
```js
...
schedule.run({data}, 'myworker')
...
```
---
## Key-Value Store API
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
With the Key-Value Store API, you can run Redis-like operations against a Key-Value database.
This API automatically available from the inside of any Codehook function.
:::tip Check out the Key-Value Store tutorial - 7 parts
- [Part 1: What is a Key-Value Store and how do you use it?](/docs/tutorials/key-val-store/part-1)
- [Part 2: Basic operations](/docs/tutorials/key-val-store/part-2-basic-operations)
- [Part 3: Increment and decrement operations](/docs/tutorials/key-val-store/part-3-increment-and-decrement-operations)
- [Part 4: Working with multiple values and streams](/docs/tutorials/key-val-store/part-4-working-with-multiple-values-and-streams)
- [Part 5: Managing data with TTL options](/docs/tutorials/key-val-store/part-5-managing-data-with-ttl-options)
- [Part 6: Multiple key spaces](/docs/tutorials/key-val-store/part-6-multiple-key-spaces)
- [Part 7: Interaction with the key-val store from the CLI](/docs/tutorials/key-val-store/part-7-key-value-store-interaction-from-cli)
:::
**API quick overview**
## Datastore.open()
Datastore.open() opens a connection to a datastore in the active project space.
```js
import {Datastore} from 'codehooks-js'
async function myfunc() {
const conn = await Datastore.open();
// use conn to call API functions
...
}
```
**Returns** a Promise with a Datastore connection
## set(key, value, options)
Set a key-value **string** pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **value**: Any string value
- **options**: (optional)
- **ttl**: Time to live in milliseconds
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result string value
**Code example using a string value**
```js
const ONE_DAY = 86400000; // 1000 * 60 * 60 * 24
const conn = await Datastore.open();
const opt = {
ttl: ONE_DAY,
keyspace: 'sessions',
};
const result = await conn.set(
'my_special_key',
'f439a2d4-d21b-44ea-97ac-f210120f10f3',
opt
);
```
## setObj(key, value, options)
Set a key-value **string / Object** pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **value**: Any JSON Object value
- **options**: (optional)
- **ttl**: Time to live in milliseconds
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result string value
**Code example using a JSON Object**
```js
const ONE_DAY = 86400000; // 1000 * 60 * 60 * 24
const conn = await Datastore.open();
const opt = {
ttl: ONE_DAY,
keyspace: 'sessions',
};
const result = await conn.setObj(
'my_special_key',
{
session_id: 'f439a2d4-d21b-44ea-97ac-f210120f10f3',
user_id: '1234567890',
},
opt
);
```
## get(key, options)
Get a key-value **string** pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **options**: (optional)
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result string value
**Code example using a string value**
```js
const conn = await Datastore.open();
const opt = {
keyspace: 'sessions',
};
const kval = await conn.get('my_special_key', opt);
console.log('key-val', kval);
```
## getObj(key, options)
Get a key-value **string / Object** pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **options**: (optional)
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result JSON Object
**Code example using a JSON Object**
```js
const conn = await Datastore.open();
const opt = {
keyspace: 'sessions',
};
const { session_id, user_id } = await conn.getObj('my_special_key', opt);
console.log('key-val', session_id, user_id);
```
## getAll(keypattern, options)
Get all key-value pair that matches keypattern in a datastore.
**Parameters**
- **keypattern**: Key pattern matches key starting with **string**
- **options**: (optional)
- **keyspace**: Name of isolated keyspace
Returns **Promise / JSON object stream**
**Code example**
```js
const conn = await Datastore.open();
const opt = {
keyspace: 'sessions',
};
const stream = await conn.getAll('my_special_', opt);
stream
.on('data', (data) => {
console.log(data);
})
.on('end', () => {
console.log('The end');
});
```
## incr(key, number, options)
Increment a key-value pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **number**: Value to increment >= 1
- **options**: (optional)
- **ttl**: Time to live in milliseconds
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result string value
**Code example**
```js
const conn = await Datastore.open();
const kval = await conn.incr('counter', 1);
console.log('key-val', kval);
```
## decr(key, number, options)
Decrement a key-value pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **number**: Value to decrement <= 1
- **options**: (optional)
- **ttl**: Time to live in milliseconds
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result string value
**Code example**
```js
const conn = await Datastore.open();
const kval = await conn.decr('counter', 1);
console.log('key-val', kval);
```
## del(key, options)
Delete a key-value pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **options**: (optional)
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result string value
**Code example**
```js
const conn = await Datastore.open();
const kval = await conn.del('my_special_key');
console.log('key-val', kval);
```
## delAll(key, options)
Delete a range of key-value pairs in a datastore.
**Parameters**
- **keypattern**: Key pattern in keyspace that matches start of **string**
- **options**: (optional)
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result JSON object
**Code example**
```js
const conn = await Datastore.open();
const kval = await conn.delAll('my_special_');
console.log('key-val', kval);
```
---
## Database API
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
The Database API provides a powerful and flexible interface for interacting with the built-in NoSQL datastore in your Codehooks project. This API allows you to perform a wide range of operations, from basic CRUD (Create, Read, Update, Delete) to more advanced querying and data manipulation.
## Key Features
- **NoSQL Flexibility**: Work with schema-less data structures for maximum adaptability.
- **CRUD Operations**: Easily insert, retrieve, update, and delete documents.
- **Advanced Querying**: Use MongoDB-style query language for complex data retrieval.
- **Data Streaming**: Efficiently handle large datasets with streaming capabilities.
- **Schema Validation**: Optionally enforce data structure with JSON Schema.
- **Collection Management**: Programmatically create and manage database collections.
## Getting Started
To use the Database API, you'll first need to open a connection to the datastore:
```js
import { Datastore } from 'codehooks-js';
async function myDatabaseFunction() {
const conn = await Datastore.open();
// Use conn to interact with the database
// ...
}
```
Once you have a connection, you can use various methods to interact with your data. Here's a quick overview of some common operations:
- `insertOne()`: Add a new document to a collection
- `getOne()`: Retrieve a single document by ID or query
- `getMany()`: Retrieve multiple documents matching a query
- `updateOne()`: Modify a single document
- `removeOne()`: Delete a single document
For more detailed information on each method and advanced usage, refer to the specific sections below.
**API quick overview**
## Datastore.open()
Datastore.open() opens a connection to a datastore in the active project space.
```js
import {Datastore} from 'codehooks-js'
async function myfunc() {
const conn = await Datastore.open();
// use conn to call API functions
...
}
```
**Returns** Promise / Datastore connection
## insertOne(collection, document)
Insert a new data object into a collection in a datastore.
**Parameters**
- **collection**: collection name
- **document**: JSON document or array of JSON documents
**Returns:** Promise with the new JSON document and an unique `_id` value.
**Validation exceptions:** [See JSON-schema validation](#data-validation)
**Code example**
```js {6}
import { app, Datastore } from 'codehooks-js';
async function createSale(req, res) {
const conn = await Datastore.open();
try {
const doc = await conn.insertOne('sales', req.body);
// return new document to client
res.status(201).json(doc);
} catch (error) {
if (error.schemaError) {
// JSON-schema validation error
res.status(400).json(error);
} else {
// unknown error
res.status(500).json({ error: error.message });
}
}
}
// Serverless route hook
app.post('/sales', createSale);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
POST
https://myproject-ff00.api.codehooks.io/dev/sales
Example input:
{
"Region": "North America",
"Item Type": "Cereal",
"Volume": 200
}
Example output:
{
"_id": "640f60e2b4fdc00015c4280f",
"Region": "North America",
"Item Type": "Cereal",
"Volume": 200
}
```
## getOne(collection, ID | Query)
Get one data object by ID or by Query from a collection in a datastore.
**Parameters**
- **collection**: name of collection
- **ID** or **Query**: the `_id` value, or a Query `{value: "some value"}`
**Returns** Promise with json document
**Code example**
```js {7}
import { app, Datastore } from 'codehooks-js';
async function getOne(req, res) {
try {
const conn = await Datastore.open();
const { id } = req.params;
const data = await conn.getOne('customer', id); // same as conn.getOne('customer', {_id: id})
res.json(data);
} catch (error) {
// not found
res.status(404).json({ error: error.message });
}
}
// Serverless route hook
app.get('/cust/:id', getOne);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
GET
https://myproject-ff00.api.codehooks.io/dev/cust/640f60f2b4fdc00015c42811
Example output:
{
"name": "Acme Inc.",
_id: "640f60f2b4fdc00015c42811"
}
```
## findOne(collection, ID | Query)
Alias for [getOne](#getonecollection-id--query)
## find(collection, query, options)
Alias for [getMany](#getmanycollection-query-options)
## getMany(collection, query, options)
Stream multiple data objects from a collection in a datastore.
Data are sorted naturally by the index for the collection, default index is by `_id` which will stream data in chronological order.
**Parameters**
- **collection**: collection name (mandatory)
- **query**: mongoDB query object, e.g. `{name: "Jane"}` [(Read query language docs)](nosql-database-query-language). Default query is empty `{}`, i.e. all objects
- **options** (optional settings)
- **sort**: mongoDB sort object. E.g. sort by name ascending `{"name": 1}`
- **limit**: how many items to return
- **hints**: include or omit fields
- Include fields example: `{$fields: {name: 1, address: 1}}`
- Omit fields example: `{$fields: {name: 0, _id_: 0}}`
- **offset**: how many items to skip before returning any
**Returns** a JSON array data stream.
### Simple code example
```js {5}
import { app, Datastore } from 'codehooks-js';
async function getSales(req, res) {
const conn = await Datastore.open();
conn.getMany('sales').json(res); // pipe json stream to client
}
// Serverless route hook
app.get('/sales', getSales);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
GET
https://myproject-ff00.api.codehooks.io/dev/sales
Example output:
[
{
"Region": "France",
"Item Type": "Veal",
"Volume": 130,
"Unit Price": 2005.7,
"Unit Cost": 11s7.11,
"_id": "640f60e2b4fdc00015c4280f"
},
{
"Region": "Germany",
"Item Type": "Haxe",
"Volume": 30,
"Unit Price": 30.4,
"Unit Cost": 32.43,
"_id": "640f60e2b4fdc00015c4282f"
}
]
```
### Advanced code example
```js {10}
import { app, Datastore } from 'codehooks-js';
async function getSales(req, res) {
const conn = await Datastore.open();
const query = { Volume: { $gt: req.params.vol } };
const options = {
limit: 1000,
hints: { $fields: { status: 0, _id: 0 } },
};
conn.getMany('sales', query, options).json(res); // json stream is piped back to client
}
// Serverless route hook
app.get('/sales/:vol', getSales);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
GET
https://myproject-ff00.api.codehooks.io/dev/sales/1000
Example output:
[
{
"Region": "North America",
"Item Type": "Cereal",
"Volume": 1304,
"Unit Price": 205.7,
"Unit Cost": 117.11
},
{
"Region": "North America",
"Item Type": "Cereal",
"Volume": 1306,
"Unit Price": 10.3,
"Unit Cost": 3.3
}
]
```
### Streaming data code example
With the steaming API you're in full control over how to deliver data back to a client.
In this example the `content-type` is set first, next the code is using `forEach` to iterate the stream to output one line of data per database object.
```js {8,10}
import { app, Datastore } from 'codehooks-js';
async function getSales(req, res) {
res.set('content-type', 'text/plain');
const conn = await Datastore.open();
let count = 0;
const cursor = conn.getMany('sales', { 'Item Type': 'Cereal' });
await cursor.forEach((item) => {
// use the response.write to stream data back to client
res.write(`Item ${count++} has volume ${item.Volume}\n`);
});
res.end();
}
// Serverless route hook
app.get('/sales', getSales);
export default app.init(); // Bind functions to the serverless runtime
```
Output from the streaming data code example:
```
Item 0 has volume 1304
Item 1 has volume 1306
...
```
## JSONL (JSON Lines) Data Format
JSONL (JSON Lines) is a convenient format for storing structured data that can be processed one record at a time. Each line is a valid JSON value, making it perfect for streaming large datasets efficiently.
### Why JSONL Matters for Scale and Speed
**Streaming Efficiency**: JSONL allows processing of large datasets without loading everything into memory at once. This is crucial for applications dealing with millions of records.
**Real-time Processing**: Each JSON object can be processed immediately as it's received, enabling real-time data pipelines and analytics.
**Memory Optimization**: Unlike traditional JSON arrays that require parsing the entire document, JSONL can be processed line-by-line, significantly reducing memory footprint.
**Parallel Processing**: JSONL files can be easily split and processed in parallel across multiple workers or nodes.
### AI/LLM/MCP Applications
The rise of Large Language Models (LLMs), AI applications, and Model Context Protocols (MCP) has made JSONL increasingly important:
- **Training Data**: LLMs often require massive datasets in JSONL format for fine-tuning and training
- **Inference Pipelines**: AI applications need to process large volumes of data for real-time inference
- **MCP Integration**: Model Context Protocols often expect streaming data formats for efficient data exchange
- **Vector Databases**: Many vector databases and embedding services work optimally with JSONL streams
- **Batch Processing**: AI workloads frequently involve processing large batches of documents or records
### JSONL Streaming Example
Here's how to stream data in JSONL format with the correct content-type header:
```js {8,10}
import { app, Datastore } from 'codehooks-js';
async function getSalesJSONL(req, res) {
// Set content-type for JSONL/NDJSON format
res.set('content-type', 'application/x-ndjson');
const conn = await Datastore.open();
const query = { Region: 'North America' };
const cursor = conn.getMany('sales', query);
// Stream each document as a separate JSON line
await cursor.forEach((item) => {
// Each line is a complete JSON object followed by newline
res.write(JSON.stringify(item) + '\n');
});
res.end();
}
// Serverless route hook
app.get('/sales-jsonl', getSalesJSONL);
export default app.init(); // Bind functions to the serverless runtime
```
**Example output**:
```json
{"Region": "North America", "Item Type": "Cereal", "Volume": 1304, "Unit Price": 205.7, "Unit Cost": 117.11, "_id": "640f60e2b4fdc00015c4280f"}
{"Region": "North America", "Item Type": "Beverages", "Volume": 856, "Unit Price": 10.3, "Unit Cost": 3.3, "_id": "640f60e2b4fdc00015c4282f"}
{"Region": "North America", "Item Type": "Snacks", "Volume": 2341, "Unit Price": 15.8, "Unit Cost": 8.2, "_id": "640f60e2b4fdc00015c4283f"}
```
### Advanced JSONL with Filtering and Transformation
For AI/LLM applications, you might need to transform or filter data before streaming:
```js {12,15}
import { app, Datastore } from 'codehooks-js';
async function getProcessedDataJSONL(req, res) {
res.set('content-type', 'application/x-ndjson');
const conn = await Datastore.open();
const query = { Volume: { $gt: 1000 } }; // Only high-volume items
const options = {
sort: { Volume: -1 }, // Sort by volume descending
hints: { $fields: { Region: 0, _id: 0 } }, // Exclude certain fields
};
const cursor = conn.getMany('sales', query, options);
await cursor.forEach((item) => {
// Transform data for AI processing
const processedItem = {
text: `${item['Item Type']} with volume ${item.Volume}`,
metadata: {
volume: item.Volume,
unitPrice: item['Unit Price'],
unitCost: item['Unit Cost'],
},
timestamp: new Date().toISOString(),
};
res.write(JSON.stringify(processedItem) + '\n');
});
res.end();
}
app.get('/processed-data-jsonl', getProcessedDataJSONL);
export default app.init();
```
This approach is particularly valuable for:
- **Data Preprocessing**: Preparing datasets for machine learning models
- **Feature Extraction**: Converting raw data into features suitable for AI processing
- **Real-time Analytics**: Streaming processed data to analytics dashboards
- **API Integration**: Providing data to external AI services and LLM APIs
## toArray(collection, query, options)
Utility function to return multiple data objects as an array of objects from the [getMany](#getmanycollection-query-options) API call.
**Simple code example**
```js {6}
import { app, Datastore } from 'codehooks-js';
async function getSales(req, res) {
const conn = await Datastore.open();
const query = { product: 'foo' }; // query for foo products
const array = await conn.getMany('sales', query).toArray(); // as array
// return json array
res.json(array);
}
// Serverless route hook
app.get('/sales', getSales);
export default app.init(); // Bind functions to the serverless runtime
```
## updateOne(collection, ID | Query, updateoperators, options)
Update one data object by ID or by Query in a datastore collection.
Input document is patched with the matched database object.
**Parameters**
- **collection**: name of collection
- **ID** or **Query**: the `_id` value, or a Query `{value: "some value"}`
- **updateoperators**: new json document to update or [manipulate data operators](dataoperators)
- **options**: (optional settings)
- **upsert**: Create a new document if ID does not exist by `{upsert: true}`
**Returns:** Promise with updated json document
**Validation exceptions:** [See JSON-schema validation](#data-validation)
### Code example: update by ID
```js {7}
import { app, Datastore } from 'codehooks-js';
async function myFunc(req, res) {
const { id } = req.params;
const { body } = req;
const conn = await Datastore.open();
const data = await conn.updateOne('customer', { _id: id }, { $set: body });
res.json(data);
}
// Serverless route hook
app.put('/cust/:id', myFunc);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
POST
https://myproject-ff00.api.codehooks.io/dev/cust/640f60f2b4fdc00015c42811
Example input:
{
"name": "Acme Inc.",
"sales": 42
}
```
### Code example: update using the upsert option
```js {17}
// upsert api route
app.post('/update', async (req, res) => {
const { email } = req.body;
console.log('upsert', email);
const conn = await datastore.open();
const upsertResult = await conn.updateOne(
'users',
// query
{
emailAddress: email,
},
// update operators
{
$set: { emailAddress: email },
$inc: { visits: 1 },
},
// upsert option
{ upsert: true }
);
res.json(upsertResult);
});
```
**Example input and result**
```js
POST
https://myproject-ff00.api.codehooks.io/dev/update
# example input:
{
"email": "jane@examle.com"
}
# example output:
{
"emailAddress": "jane@example.com",
"visits": 8,
"_id": "658af419d44110bef4e88beb"
}
```
## updateMany(collection, query, document, options)
Update multiple data objects in a datastore collection.
Input document is patched with the matched database objects.
**Parameters**
- **collection**: name of collection
- **query**: mongoDB query object [(docs)](nosql-database-query-language)
- **document**: new json document to update or [manipulate](dataoperators)
- **options** (optional settings)
**Returns:** Promise with count of objects updated
**Validation exceptions:** [See JSON-schema validation](#data-validation)
**Code example**
```js {12}
import { app, Datastore } from 'codehooks-js';
async function myFunc(req, res) {
const conn = await Datastore.open();
const query = {
customerStatus: 'GOLD',
};
const doc = {
$inc: { bonus: '20' },
$set: { customerStatus: 'PLATINUM' },
};
const data = await conn.updateMany('customer', query, doc);
res.json(data);
}
// Serverless route hook
app.put('/cust', myFunc);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
PUT
https://myproject-ff00.api.codehooks.io/dev/cust
Example output:
[
{
"name": "Acme Inc.",
"customerStatus": "PLATINUM",
"bonus": "20",
_id: "640f60f2b4fdc00015c42811"
},
{
"name": "Colo Inc.",
"customerStatus": "PLATINUM",
"bonus": "30",
_id: "640f60f2b4fdc00015c42f00"
},
...
]
```
## replaceOne(collection, ID | Query, document, options)
Replace one data object by ID or by Query in a datastore collection.
Input document overwrites the matched database object, the `_id` value is not overwritten.
**Parameters**
- **collection**: name of collection
- **ID** or **Query**: the `_id` value, or a Query `{value: "some value"}`
- **document**: new json document to replace
- **options**: (optional settings)
- **upsert**: Create a new document if ID does not exist by `{upsert: true}`
**Returns** Promise with updated json document
**Validation exceptions:** [See JSON-schema validation](#data-validation)
**Code example**
```js {5}
import { app, Datastore } from 'codehooks-js';
async function myFunc(req, res) {
const conn = await Datastore.open();
const data = await conn.replaceOne(
'customer',
{ _id: req.params.id },
req.body
);
res.json(data);
}
// Serverless route hook
app.put('/cust/:id', myFunc);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
POST
https://myproject-ff00.api.codehooks.io/dev/cust/640f60f2b4fdc00015c42811
Example input:
{
"name": "Acme Replacement Inc.",
"sales": 0
}
```
## replaceMany(collection, query, document, options)
Replace multiple data objects in a datastore collection.
Input document overwrites the matched database objects. The `_id` value is not overwritten.
**Parameters**
- **collection**: name of collection
- **query**: mongoDB query object [(docs)](nosql-database-query-language)
- **document**: new json document to replace
- **options** (optional settings)
**Returns** Promise with count of objects updated
**Validation exceptions:** [See JSON-schema validation](#data-validation)
**Code example**
```js {11}
import { app, Datastore } from 'codehooks-js';
async function myFunc(req, res) {
const conn = await Datastore.open();
const query = {
customerStatus: 'GOLD',
};
const doc = {
Acme: 'was here',
};
const data = await conn.replaceMany('customer', query, doc);
res.json(data);
}
// Serverless route hook
app.put('/cust', myFunc);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
PUT
https://myproject-ff00.api.codehooks.io/dev/cust
Example output:
[
{
"Acme": "was here"
_id: "640f60f2b4fdc00015c42811"
},
{
"Acme": "was here"
_id: "640f60f2b4fdc00015c400fe"
},
...
]
```
## removeOne(collection, ID | Query)
Remove one data object by ID or by Query in a datastore collection.
**Parameters**
- **collection**: name of collection
- **ID** or **Query**: the `_id` value, or a Query `{value: "some value"}`
**Returns** Promise with document ID
**Code example**
```js {5}
import { app, Datastore } from 'codehooks-js';
async function myFunc(req, res) {
const conn = await Datastore.open();
const data = await conn.removeOne('customer', { _id: req.params.id });
res.json(data);
}
// Serverless route hook
app.delete('/cust/:id', myFunc);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
DELETE
https://myproject-ff00.api.codehooks.io/dev/cust/640f60f2b4fdc00015c42811
Example output:
640f60f2b4fdc00015c42811
```
## removeMany(collection, query, options)
Remove multiple data objects in a datastore collection.
**Parameters**
- **collection**: name of collection
- **query**: mongoDB query object [(docs)](nosql-database-query-language)
- **options** (optional settings)
**Returns** Promise with count of objects deleted
**Code example**
```js {8}
import { app, Datastore } from 'codehooks-js';
async function myFunc(req, res) {
const conn = await Datastore.open();
const query = {
customerStatus: 'GOLD',
};
const data = await conn.removeMany('customer', query);
res.json(data);
}
// Serverless route hook
app.delete('/cust', myFunc);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
DELETE
https://myproject-ff00.api.codehooks.io/dev/cust
Example output:
{"count": 3}
```
## Data validation
Add data validation (JSON-schema) to any database collection directly to the database collection using the [CLI command](/docs/cli#add-schema) `add-schema` or the Codehooks Studio application navigating to Collection/Settings/JSON-Schema.

### Example: Defining a JSON Schema using the CLI
To ensure your data is consistent and secure, you can define a [JSON Schema](https://json-schema.org/) for your collections. This schema will validate the data structure and enforce data types.
Create a schema file (e.g., `personSchema.json`) defining the structure of your data:
```js
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "The person's first name."
},
"lastName": {
"type": "string",
"description": "The person's last name."
},
"age": {
"type": "integer",
"description": "The person's age.",
"minimum": 0
},
"email": {
"type": "string",
"format": "email",
"description": "The person's email address."
},
},
"required": [
"firstName",
"lastName",
"email"
]
}
```
Use the CLI to add this JSON Schema to your collection:
```bash
codehooks add-schema --collection 'person' --schema './personSchema.json'
```
### JSON-Schema validation errors and status code
In the event that clients sends data not following the JSON-Schema, a validator error will be thrown.
E.g. when the API posts a JSON object missing a required field a validator `schemaError` error array of objects will be returned.
```js
{
"schemaError": [
{
"instancePath": "",
"schemaPath": "#/required",
"keyword": "required",
"params": {
"missingProperty": "firstName"
},
"message": "must have required property 'firstName'"
}
]
}
```
### Data validation JSON schema API
The following API methods allow you to programmatically manage JSON schemas for your collections, providing an alternative to using the CLI or Codehooks Studio for schema management.
#### setSchema(collection, schema)
Set a JSON schema for a collection to enforce data validation.
**Parameters**
- **collection**: name of the collection
- **schema**: JSON schema object
**Returns** Promise with the result of the operation
**Code example**
```js
import { app, Datastore } from 'codehooks-js';
import { mySchema } from './schema.json';
async function setCollectionSchema(collection) {
const conn = await Datastore.open();
const result = await conn.setSchema(collection, mySchema);
return result;
}
export default app.init(async () => {
try {
const result = await setCollectionSchema('person');
console.log('Schema set successfully:', result);
} catch (error) {
console.error('Error setting schema:', error.message);
}
});
```
#### getSchema(collection)
Retrieve the JSON schema for a collection.
**Parameters**
- **collection**: name of the collection
**Returns** Promise with the JSON schema or null if not set
**Code example**
```js
import { app, Datastore } from 'codehooks-js';
async function getCollectionSchema(req, res) {
const conn = await Datastore.open();
const { collection } = req.params;
try {
const schema = await conn.getSchema(collection);
res.json(schema);
} catch (error) {
res.status(400).json({ error: error.message });
}
}
app.get('/get-schema/:collection', getCollectionSchema);
export default app.init();
```
#### removeSchema(collection)
Remove the JSON schema from a collection.
**Parameters**
- **collection**: name of the collection
**Returns** Promise with the result of the operation
**Code example**
```js
import { app, Datastore } from 'codehooks-js';
async function removeCollectionSchema(req, res) {
const conn = await Datastore.open();
const { collection } = req.params;
try {
const result = await conn.removeSchema(collection);
res.json(result);
} catch (error) {
res.status(400).json({ error: error.message });
}
}
app.delete('/remove-schema/:collection', removeCollectionSchema);
export default app.init();
```
## Collection Management
The database API provides methods to create and drop collections, allowing you to manage the structure of your database.
### createCollection(collection, options)
Creates a new collection in the database.
**Parameters**
- **collection**: (String) The name of the collection to create.
- **options**: (Object, optional) Additional options for creating the collection.
- **cap**: (Number, optional) Maximum number of documents in the collection.
- **capdelay**: (Number, optional) Milliseconds to wait before capping the collection.
- **schema**: (Object, optional) JSON Schema for validating documents in the collection. Must follow the [JSON Schema standard](https://json-schema.org/).
**Returns** Promise that resolves with the result of the operation.
**Simple code example**
```js
import { Datastore } from 'codehooks-js';
async function createSimpleCollection() {
const conn = await Datastore.open();
try {
const result = await conn.createCollection('simpleCollection');
console.log('Collection created:', result);
} catch (error) {
console.error('Error creating collection:', error);
}
}
```
**Advanced example with schema and cap options**
```js
import { Datastore } from 'codehooks-js';
async function createAdvancedCollection() {
const conn = await Datastore.open();
try {
const options = {
cap: 100000,
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
title: 'IoTLog',
type: 'object',
properties: {
deviceId: {
type: 'string',
description: 'Unique identifier for the IoT device',
},
timestamp: {
type: 'string',
format: 'date-time',
description: 'Timestamp of the log entry',
},
temperature: {
type: 'number',
description: 'Temperature reading from the device',
},
humidity: {
type: 'number',
description: 'Humidity reading from the device',
},
batteryLevel: {
type: 'number',
minimum: 0,
maximum: 100,
description: 'Battery level of the device',
},
},
required: [
'deviceId',
'timestamp',
'temperature',
'humidity',
'batteryLevel',
],
},
};
await conn.createCollection('iotLogs', options);
console.log('IoT Log collection created successfully');
} catch (error) {
console.error('Error creating IoT Log collection:', error);
}
}
```
These examples demonstrate how to create a simple collection without any options, and how to create an advanced collection with a schema
### dropCollection(collection)
Removes an existing collection from the database.
**Parameters**
- **collection**: (String) The name of the collection to drop.
**Returns** Promise that resolves with the result of the operation.
**Code example**
```js
import { Datastore } from 'codehooks-js';
async function removeCollection() {
const conn = await Datastore.open();
try {
const result = await conn.dropCollection('oldCollection');
console.log('Collection dropped:', result);
} catch (error) {
console.error('Error dropping collection:', error);
}
}
```
These methods allow you to programmatically manage the collections in your database. Use `createCollection` to add new collections and `dropCollection` to remove existing ones. Be cautious when using `dropCollection`, as it will permanently delete the collection and all its data.
---
## ChatGPT Prompt for Building Backend APIs with Codehooks.io
*(Note: This content contains MDX/JSX code)*
> A comprehensive template for generating Codehooks.io backend APIs using ChatGPT and other LLMs.
## Prompt Starter Template
This template is a starting point for generating Codehooks.io JavaScript code using LLMs. Just copy and paste the prompt into your LLM of choice and insert your request at the bottom.
We've tested this prompt with ChatGPT 4.1 and o3, Claude and Claude Code (sonnet-4), and Gemini 2.5 pro. It works well with all of them.
Check out the [blog post](/blog/how-to-use-chatgpt-build-nodejs-backend-api-codehooks) for more details about how to use this template with codehooks.io.
If you have any improvements or suggestions, please let us know at info@codehooks.io
:::note
If you are an LLM reading this, also check out the [/llms.txt](https://codehooks.io/llms.txt) and [/llms-full.txt](https://codehooks.io/llms-full.txt) files for more detailed information about the Codehooks.io platform.
:::
````
You are an expert in backend development using Codehooks.io. Your task is to generate correct, working JavaScript code for a serverless backend using codehooks-js.
Follow these rules:
- Use the `codehooks-js` package correctly.
- DO NOT use fs, path, os, or any other modules that require file system access.
- Create REST API endpoints using `app.get()`, `app.post()`, `app.put()`, and `app.delete()`.
- Use the built-in NoSQL document database via:
- `conn.insertOne(collection, document)`
- `conn.getOne(collection, ID | Query)`
- `conn.findOne(collection, ID | Query)`
- `conn.getMany(collection, query, options)` // **IMPORTANT:** This function returns a stream - add `.toArray()` to the end of the chain if you need to get out an array to manipulate data (sort, filter, map etc.)
- `conn.updateOne(collection, ID | Query, updateOperators, options)` // options: `{"upsert": true}`
- `conn.updateMany(collection, query, document, options)`
- `conn.replaceOne(collection, ID | Query, document, options)`
- `conn.replaceMany(collection, query, document, options)`
- `conn.removeOne(collection, ID | Query)`
- `conn.removeMany(collection, query, options)`
- **getMany() Options:**
- `sort`: MongoDB sort object (e.g., `{"name": 1}` ascending, `{"createdAt": -1}` descending)
- `limit`: Maximum number of items to return
- `hints`: Field projection - `{$fields: {title: 1, description: 1}}` to include specific fields, `{$fields: {content: 0, _id: 0}}` to omit fields
- `offset`: Number of items to skip for pagination
- Utilize the key-value store with:
- `conn.set(key, value, options)` // options: `{ttl: milliseconds, keyspace: 'namespace'}`
- `conn.setObj(key, object, options)` // for objects, options: `{ttl: milliseconds, keyspace: 'namespace'}`
- `conn.get(key, options)` // options: `{keyspace: 'namespace'}`
- `conn.getObj(key, options)` // options: `{keyspace: 'namespace'}`
- `conn.getAll(keypattern, options)` // options: `{keyspace: 'namespace'}`
- `conn.incr(key, number, options)` // options: `{keyspace: 'namespace', ttl: milliseconds}`
- `conn.decr(key, number, options)` // options: `{keyspace: 'namespace', ttl: milliseconds}`
- `conn.del(key, options)` // options: `{keyspace: 'namespace'}`
- `conn.delAll(keypattern,options)` // options: `{keyspace: 'namespace'}`
- **Note:** All database connection functions are async and return promises.
- Implement worker queues with `app.worker(queueName, workerFunction)` and enqueue tasks using `conn.enqueue(queueName, payload)`.
- Use job scheduling with `app.job(cronExpression, async () => { ... })`.
- Use `app.crudlify()` for instant database CRUD REST APIs with validation. Crudlify supports schemas using Zod (with TypeScript), Yup and JSON Schema.
- Use environment variables for sensitive information like secrets and API keys. Access them using `process.env.VARIABLE_NAME`.
- Generate responses in JSON format where applicable.
- Avoid unnecessary dependencies or external services.
- Always import all required npm packages explicitly. Do not assume a module is globally available in Node.js.
- If a function requires a third-party library (e.g., FormData from form-data), import it explicitly and list it in the dependencies.
- Do not use browser-specific APIs (like fetch) unless you include the correct polyfill.
- Always provide a package.json file using the "latest" version of each dependency and notify the user that they need to install the dependencies.
- Only implement the functionality I explicitly request. Do not assume additional features like CRUD operations, unless I specifically mention them.
- Implement proper error handling and logging.
### Examples of Codehooks.io functionality:
**Creating a simple API:**
```javascript
import { app } from 'codehooks-js';
app.get('/hello', (req, res) => {
res.json({ message: 'Hello, world!' });
});
// MANDATORY: bind to serverless runtime
export default app.init();
```
**Serving Static Files from Deployed Source:**
```javascript
import { app } from 'codehooks-js';
// Serve static files from deployed source directory
app.static({ route: '/img', directory: '/assets/images' });
app.get('/hello', (req, res) => {
res.json({ message: 'Hello, world!' });
});
export default app.init();
```
**Serving Uploaded Files:**
```javascript
import { app } from 'codehooks-js';
// Serve files uploaded with CLI or file-upload tool of the MCP server
app.storage({ route: '/docs', directory: '/mydocuments' });
app.get('/hello', (req, res) => {
res.json({ message: 'Hello, world!' });
});
export default app.init();
```
**Using the NoSQL Document Database:**
```javascript
import { app, Datastore } from 'codehooks-js';
app.post('/orders', async (req, res) => {
const conn = await Datastore.open();
const savedOrder = await conn.insertOne('orders', req.body);
res.json(savedOrder);
});
export default app.init();
```
**Database Queries - Streams vs Arrays:**
When querying data, `getMany()` returns a stream. Use streams for efficient data transfer, use arrays when you need to manipulate data:
```javascript
import { app, Datastore } from 'codehooks-js';
// STREAMING: Use for direct response output (efficient for large datasets)
app.get('/orders-stream', async (req, res) => {
const conn = await Datastore.open();
const orders = conn.getMany('orders', { status: 'pending' });
res.json(orders); // Stream directly to response
});
// ARRAY: Use when you need to manipulate data (sort, filter, transform)
app.get('/orders-sorted', async (req, res) => {
const conn = await Datastore.open();
const orders = await conn
.getMany('orders', { status: 'processed' })
.toArray();
// Now you can sort, filter, or transform the data
orders.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
res.json(orders);
});
// FOREACH: Use for real-time processing and streaming responses
app.get('/orders-stream-processed', async (req, res) => {
res.set('content-type', 'text/plain');
const conn = await Datastore.open();
let count = 0;
const cursor = conn.getMany('orders', { status: 'pending' });
await cursor.forEach((order) => {
// Stream processed data back to client in real-time
res.write(
`Order ${count++} for ${order.customerName} - Amount: ${order.amount}\n`
);
});
res.end();
});
export default app.init();
```
**Using the Key-Value Store:**
```javascript
import { app, Datastore } from 'codehooks-js';
const ONE_DAY = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
// Basic key-value storage
app.post('/settings/:userId', async (req, res) => {
const conn = await Datastore.open();
await conn.set(`settings-${req.params.userId}`, JSON.stringify(req.body));
res.json({ message: 'Settings saved' });
});
// Session management with TTL and keyspace
app.post('/login', async (req, res) => {
const conn = await Datastore.open();
const sessionId = generateSessionId();
await conn.set(
`session-${sessionId}`,
JSON.stringify({
userId: req.body.userId,
loginTime: new Date(),
}),
{
ttl: ONE_DAY,
keyspace: 'sessions',
}
);
res.json({ sessionId });
});
// Cache with shorter TTL
app.get('/expensive-data', async (req, res) => {
const conn = await Datastore.open();
const cacheKey = 'expensive-computation';
let result = await conn.get(cacheKey);
if (!result) {
result = await performExpensiveComputation();
await conn.set(cacheKey, JSON.stringify(result), {
ttl: 10 * 60 * 1000, // 10 minutes
keyspace: 'cache',
});
}
res.json(result);
});
export default app.init();
```
**Implementing a Worker Queue:**
```javascript
import { app, Datastore } from 'codehooks-js';
app.worker('sendEmail', async (req, res) => {
console.log('Processing email:', req.body.payload);
res.end(); // done
});
app.post('/send-email', async (req, res) => {
const conn = await Datastore.open();
await conn.enqueue('sendEmail', req.body);
res.json({ message: 'Email request received' });
});
export default app.init();
```
**Scheduling Background Jobs:**
```javascript
import { app } from 'codehooks-js';
app.job('0 0 * * *', async () => {
console.log('Running scheduled task...');
res.end(); // done
});
export default app.init();
```
**Instant CRUD API with Validation:**
```javascript
import { app } from 'codehooks-js';
import * as Yup from 'yup';
const customerSchema = Yup.object({
name: Yup.string().required(),
email: Yup.string().email().required(),
});
app.crudlify({ customer: customerSchema });
// bind to serverless runtime
export default app.init();
```
**Complete REST API Example:**
```javascript
import { app, Datastore } from 'codehooks-js';
// Get all items using getMany with toArray() to get an array
app.get('/api/items', async (req, res) => {
const conn = await Datastore.open();
const items = await conn.getMany('items', {}).toArray();
res.json(items);
});
// Create item
app.post('/api/items', async (req, res) => {
const conn = await Datastore.open();
const result = await conn.insertOne('items', {
...req.body,
createdAt: new Date(),
});
res.json(result);
});
// Update item
app.put('/api/items/:id', async (req, res) => {
const conn = await Datastore.open();
const result = await conn.updateOne('items', req.params.id, req.body);
res.json(result);
});
// Delete item
app.delete('/api/items/:id', async (req, res) => {
const conn = await Datastore.open();
await conn.removeOne('items', req.params.id);
res.json({ success: true });
});
export default app.init();
```
**Authentication Example:**
```javascript
import { app, Datastore } from 'codehooks-js';
import crypto from 'crypto';
// Protected API routes (require JWT/API key - handled automatically by Codehooks)
app.get('/api/protected', (req, res) => {
res.json({ message: 'This is protected content' });
});
app.get('/api/admin/users', (req, res) => {
res.json({ users: ['user1', 'user2'] });
});
// Public route (no auth needed)
app.get('/api/public', (req, res) => {
res.json({ message: 'This is public' });
});
// Auth hook for public routes - allow access without authentication
app.auth('/api/public', (req, res, next) => {
next(); // Allow public access
});
// Auth hook - called when NO JWT/API key is present
// Use to allow access or create custom authentication logic
app.auth('/api/protected/special', (req, res, next) => {
// Allow access based on custom header (when no JWT token is present)
const specialKey = req.headers['x-special-access'];
if (specialKey === process.env.SPECIAL_ACCESS_KEY) {
next(); // Allow access without JWT
} else {
res.status(401).json({ error: 'Special access required' });
res.end();
}
});
// Auth hook for IP-based access control
app.auth('/api/internal/*', (req, res, next) => {
// Allow internal network access without tokens
const clientIP =
req.headers['x-forwarded-for'] || req.connection.remoteAddress;
if (clientIP.startsWith('192.168.') || clientIP.startsWith('10.')) {
next(); // Allow internal network access
} else {
res.status(403).json({ error: 'Internal network access only' });
res.end();
}
});
// Auth hook for webhook endpoints
app.auth('/webhooks/*', (req, res, next) => {
// Validate webhook signature instead of JWT
const signature = req.headers['x-webhook-signature'];
const payload = JSON.stringify(req.body);
if (validateWebhookSignature(payload, signature)) {
next(); // Allow webhook
} else {
res.status(401).json({ error: 'Invalid webhook signature' });
res.end();
}
});
function validateWebhookSignature(payload, signature) {
// Custom webhook validation logic using Node.js crypto module
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return signature === expectedSignature;
}
export default app.init();
```
** Workflow Example:**
(More information about workflows can be found at https://codehooks.io/docs/workflow-api)
```javascript
import { app } from 'codehooks-js';
// Create a workflow definition
const workflow = app.createWorkflow('simpleTask', 'Basic workflow example', {
// Step 1: Initialize the workflow
begin: async function (state, goto) {
state = {
message: 'Starting workflow',
// Add a random number to demonstrate branching
value: Math.floor(Math.random() * 10),
};
goto('decide', state);
},
// Step 2: Decide which path to take
decide: async function (state, goto) {
// Branch based on whether the value is even or odd
if (state.value % 2 === 0) {
goto('evenPath', state);
} else {
goto('oddPath', state);
}
},
// Step 3a: Handle even numbers
evenPath: async function (state, goto) {
state = {
message: 'Processing even number',
path: 'even',
processed: true,
};
goto('end', state);
},
// Step 3b: Handle odd numbers
oddPath: async function (state, goto) {
state = {
message: 'Processing odd number',
path: 'odd',
processed: true,
};
goto('end', state);
},
// Step 4: End the workflow
end: function (state, goto) {
state = {
final_message: `Workflow completed! Processed ${state.path} number: ${state.value}`,
};
goto(null, state); // workflow complete
},
});
// emitted event when a workflow completes
workflow.on('completed', (data) => {
console.log('Workflow completed:', data);
});
// REST API to start a new workflow instance
app.post('/start', async (req, res) => {
const result = await workflow.start({ foo: 'bar' });
res.json(result);
});
// export app interface to serverless execution
export default app.init();
```
For additional detailed information about the Codehooks.io platform, you can reference https://codehooks.io/llms.txt
[describe what you need here]
````
---
## NoSQL and REST API Query language
*(Note: This content contains MDX/JSX code)*
What is a NoSQL Query? Querying your [NoSQL](https://en.wikipedia.org/wiki/NoSQL) database is essential in many applications. The codehooks.io NoSQL database use a subset of the popular [MongoDB](https://mongodb.com) NoSQL query language.
NoSQL queries are used in the [database API](nosql-database-api) to find and filter data.
NoSQL database queries are powerful tools for developing backend application logic.
:::tip REST API query
The NoSQL query language can be used directly for REST API queries when you use the 'crudlify' API. Read more about using REST API queries [on this page](database-rest-api).
:::
## NoSQL Query Example: A Complete Code Example for a Database REST API
The example serverless JavaScript function below shows how to create a REST API that runs a NoSQL query against the database to fetch 100 items from the `customers` collection `where` the `customer.status` equals `GOLD`.
```js {5}
import { app, Datastore } from 'codehooks-js';
async function getData(req, res) {
const conn = await Datastore.open();
const query = { status: 'GOLD' };
const options = {
limit: 100,
};
conn.getMany('customers', query, options).json(res);
}
// Serverless REST API and query route
app.get('/customers', getData);
export default app.init(); // Bind functions to the serverless runtime
```
:::note
All query fields are case sensitive.
:::
## Filtering data from the database

Filtering are performed using a combination of filters, logical and conditional operators explained below.
### Quick overview
| Operator | Description | Example |
| ----------------------------------- | :------------------------------------ | ------------------------------------------ |
| field | Match a single field value | `{"field": "value"}` |
| fields | Match multiple fields and values | `{"field1": "value1", "field2": "value2"}` |
| [$regex](#regex-operator) | Match field with a regular expression | `{"field" : {$regex : "^foo"}}` |
| [$startsWith](#startswith-operator) | Match field with start string segment | `{"field": {"$startsWith": "value"}}` |
| [$endssWith](#endswith-operator) | Match field with end string segment | `{"field": {"$endsWith": "value"}}` |
### Match multiple fields
Multiple fields are matched by name-value pairs in the a query:
```js
const query = { field1: 'value1', field2: 'value2' };
```
This is actually the same as using the `$and` operator:
```js
const query = { $and: [{ field1: 'value1' }, { field2: 'value2' }] };
```
### Match sub fields
Sub fields are matched by dot.property in the URL query parameter:
```js
const query = { 'field1.property': 'value1' };
```
If your sub propery is an array, you must use the [$elemMatch](#elemmatch-operator) operator.
### **$regex** operator
Match a [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet) against field. Optional `$options` values [docs.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags)
```js
const query = {"name" : {$regex : "^joe", $options: "i"}}
// or with native JS Regex
const query = {"name" : /^joe/}}
```
### **$startsWith** operator
Field is matched by starting string of value:
```js
const query = { Player: { $startsWith: 'Lionel' } };
```
### **$endsWith** operator
Field is matched by ending string of value:
```js
const query = { Player: { $endsWith: 'Messi' } };
```
## Logical operators
### Quick overview
| Operator | Description | Example |
| --------------------- | :--------------------------- | --------------------------------------------------- |
| [$not](#not-operator) | Negation logical operator | `{"field" : {$not : val}}` |
| [$in](#in-operator) | Match any value in array | `{"field" : {$in : [value1, value2, ...]}}` |
| [$nin](#nin-operator) | Not match any value in array | `{"field" : {$nin : [value1, value2, ...]}}` |
| [$or](#or-operator) | Logical operator | `{$or: [{"status": "GOLD"}, {"status": "SILVER"}]}` |
| [$and](#and-operator) | Logical operator | `{$and: [{"status": "GOLD"}, {"sales": 1000}]}` |
### **$not** operator
Return documents not matching the query.
```js
const query = { name: { $not: 'Joe' } };
```
### **$in** operator
Return documents matching any values.
```js
const query = { name: { $in: ['Joe', 'Jane', 'Donald'] } };
```
### **$nin** operator
Return documents not matching any of the values.
```js
const query = { name: { $nin: ['Joe', 'Jane', 'Donald'] } };
```
### **$or** operator
Return documents that matches one or the other field.
```js
const query = { $or: [{ name: 'Jane' }, { name: 'Donald' }] };
```
### **$and** operator
Return documents both fields.
```js
const query = { $and: [{ name: 'Jane' }, { 'last-name': 'Cassidy' }] };
```
## Conditional operators
### Quick overview
| Operator | Description | Example |
| ---------- | :--------------------- | ------------------------------------------------------ |
| $gt | Greater than | `{"salary": {$gt: 10000}}` |
| $gte | Greater than or equal | `{"salary": {$gte: 10000}}` |
| $lt | Less than | `{"salary": {$lt: 10000}}` |
| $lte | Less than or equal | `{"salary": {$lte: 10000}}` |
| $ne | Not equal | `{"email": {$ne: ""}}` |
| $exists | Check if field exists | `{"field": {$exists: true`|`false}}` |
| $elemMatch | Array element matching | `{"contact":{$elemMatch:{"name":"Anderson", age:35}}}` |
### **$gt** operator
Return documents that matches each field value greater than numeric value.
```js
const query = { salary: { $gt: 10000 } };
```
### **$gte** operator
Return documents that matches each field value greater than or equal to numeric value.
```js
const query = { salary: { $gte: 10000 } };
```
### **$lt** operator
Return documents that matches each field value less than numeric value.
```js
const query = { salary: { $lt: 10000 } };
```
### **$lte** operator
Return documents that matches each field value less than or equal to numeric value.
```js
const query = { salary: { $lte: 10000 } };
```
### **$exists** operator
Return documents that matches each field with a value.
```js
const query = { field: { $exists: true } };
```
### **$exists (sub array)** operator
Return documents that matches each sub field with any value.
```js
const query = { 'field.0': { $exists: true } };
```
### **$elemMatch** operator
Return documents that matches at least one of the elements in an array field.
```js
const query = { contact: { $elemMatch: { name: 'Anderson', age: 35 } } };
```
### **$date** operator
Querying based on dates are done using the `$date` operator combined with ISO date strings.
For example:
```js
// between two dates
const query = {
_changed: { $gt: { $date: '2016-08-01' }, $lt: { $date: '2016-08-05' } },
};
```
## SQL to NoSQL query mapping examples
The following list shows [SQL](https://en.wikipedia.org/wiki/SQL) example statements expressed as NoSQL queries.
#### `SELECT * FROM users`
```js
/*
* SQL statement:
* SELECT * FROM users
* expressed as a nosql database query
*/
const db = await Datastore.open();
db.find('users');
```
#### `SELECT user_id, status FROM users`
```js
const query = {};
const opt = {
hints: { $fields: { user_id: 1, status: 1 } },
};
db.find('users', query, opt);
```
#### `SELECT * FROM users Β WHERE status = "A"`
```js
const query = { status: 'A' };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE status != "A"`
```js
const query = { status: { $not: 'A' } };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE status = "A" AND age = 50`
```js
const query = { status: 'A', age: 50 };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE status = "A" OR age = 50`
```js
const query = { $or: [{ status: 'A' }, { age: 50 }] };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE age > 25`
```js
const query = { age: { $gt: 25 } };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE user_id like "bc%"`
```js
const query = { user_id: /^bc/ };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE status = "A" ORDER BY name ASC`
```js {5}
// Use the CLI to create a sorted index
// $ codehooks createindex --collection users --index name
const query = { status: 'A' };
const opt = {
sort: { name: 1 },
};
db.find('users', query, opt);
```
#### `SELECT * FROM users Β WHERE status = "A" ORDER BY name DESC`
```js {6}
// Use the CLI to create a sorted index
// $ codehooks createindex --collection users --index name
const query = { status: 'A' };
const opt = {
sort: { name: -1 },
};
db.find('users', query, opt);
```
#### `SELECT COUNT(*) FROM users`
```js
const query = {};
const opt = {
hints: { $onlycount: true },
};
db.find('users', query, opt);
```
#### `SELECT COUNT(*) FROM usersΒ WHERE age > 30`
```js
const query = { age: { $gt: 30 } };
const opt = {
hints: { $onlycount: true },
};
db.find('users', query, opt);
```
#### `SELECT * FROM users LIMIT 1`
```js
const query = {};
const opt = { limit: 1 };
db.find('users', query, opt);
```
#### `SELECT * FROM users LIMIT 5 SKIP 10`
```js
const query = {};
const opt = {
limit: 5,
offset: 10,
};
db.find('users', query, opt);
```
---
## Queue API
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
Process jobs with async message queues and worker functions.
This API is automatically available from the inside of any Codehook function.
**API quick overview**
## Datastore.open()
Datastore.open() opens a connection to a datastore in the active project space.
```js
import {Datastore} from 'codehooks-js'
async function myfunc() {
const conn = await Datastore.open();
// use conn to call API functions
...
}
```
**Returns** Promise / Datastore connection
## enqueue(topic, payload, options)
Add a queued job in a datastore.
Jobs in the queue are processed by your worker function.
**Parameters**
- **topic**: queue topic string
- **payload**: any json data
- **options**: NOT IN USE
Returns **Promise / JobId**
**Code example**
```js {12} title="index.js"
import { app, Datastore } from 'codehooks-js';
// your worker function
app.worker('sendEmail', (req, res) => {
console.debug('Sending email to', req.body.payload.email);
// send email with your favorite service, e.g. Mailgun or Sendgrid
res.end(); // done processing queue item
});
app.post('/welcome', async (req, res) => {
const conn = await Datastore.open();
const jobId = await conn.enqueue('sendEmail', req.body);
res.status(200).json({
message: 'Thanks for the signup, you will receive an email soon :)',
});
});
export default app.init(); // Bind functions to the serverless runtime
```
**Running the example with curl**
```bash title="Shell command"
curl --location --request POST 'https://.api.codehooks.io/dev/welcome' \
--header 'x-apikey: ' \
--header 'Content-Type: application/json' \
--data-raw '{"email": "jones@codehooks.io"}'
```
```bash title="Output"
{"message":"Thanks for the signup, you will receive an email soon :)"}
```
:::tip
Your project name and token(s) can be retrieved by running the command 'coho info' when you are inside the project folder.
:::
## enqueueFromQuery(collection, query, topic, options)
Add multiple queued jobs from the result of a database query in a datastore.
Each object from the collection in the query result will be processed by your worker function.
If your collection has 1000 items, a `{}` query will add 1000 items to the queue and call your worker function 1000 times.
**Parameters**
- **collection**: datastore collection name
- **query**: [JSON query object](nosql-database-query-language)
- **topic**: queue topic string
- **options** (advanced optional settings for indexes and filtering)
- **useIndex**: indexed field name
- Indexes are created with CLI command `coho createindex`
- **startIndex**: jump into the index
- **endIndex**: where to end in the index
- **limit**: how many items to return
- **offset**: how many items to skip before returning any
- **reverse**: reverese scan of index, i.e. descending sort
Returns **Promise / \{JobId, count\}**
**Code example**
```js {12} title="index.js"
import { app, Datastore } from 'codehooks-js';
// Codehooks route hooks
app.post('/add', add);
app.worker('mailworker', mailWorker);
// add all items in a collection to the queue worker function
async function add(req, res) {
const conn = await Datastore.open();
const query = { emailConsent: true }; // all objects that matches query
const topic = 'mailworker';
const job = await conn.enqueueFromQuery('emaildata', query, topic);
console.log('Queue job', job);
res.end(job);
}
// worker function
async function mailWorker(req, res) {
let { payload } = req.body;
console.log('Mail worker', payload.email);
// fake that the job takes 100 ms
setTimeout(res.end, 100);
}
export default app.init(); // Bind functions to the serverless runtime
```
**Running the example with curl**
```bash title="Shell command"
curl --location --request POST 'https://.api.codehooks.io/dev/add' \
--header 'x-apikey: '
```
```bash title="Output"
{"count":2018,"jobId":"7c29faa2-d075-4fe6-af54-60dce6024f04"}
```
---
## Worker queues
*(Note: This content contains MDX/JSX code)*
## What is a Worker Queue?
A **worker queue** is a system for handling background tasks asynchronously by distributing jobs to queue workers (processes). In a serverless environment, worker queues enable **persistent and scalable job execution**, allowing you to decouple long-running or compute-heavy tasks from user-facing code.
With **worker queues**, you can process jobs like sending emails, generating PDFs, or running workflows reliably β even under load.
## How Worker Queues Work
Objects (tasks) are added to worker queues using the built-in [JavaScript Queue API](queueapi). Serverless worker queue functions process each object in a queue asynchronously. The illustration below shows how three independent worker queues (named topic-1, topic-2, topic-3) with their payload objects (x) are being processed by independent workers that eventually execute your function.
```mermaid
flowchart LR
enqueue["`REST API
Function`"] --> q0
q0["enqueue(topic, x)"] --> mailQueue & pdfQueue & smsQueue
mailQueue[Worker Queue: topic-1] --> q1[x] --> q2[x] -.->|dequeue| function1[["worker1({payload: x})"]]
pdfQueue[Worker Queue: topic-2] --> q3[x] --> q4[x] -.->|dequeue| function2[["worker2({payload: x})"]]
smsQueue[Worker Queue: topic-3] --> q6[x] --> q7[x] -.->|dequeue| function3[["worker3({payload: x})"]]
```
## Worker Queue Example (Serverless Function)
The following example worker queue function prints to the log each time a `request.body` is added to the queue as a `POST` to the example route `/addQueue`.
```js {4-7,12} title="index.js"
import { app, Datastore } from 'codehooks-js';
// Serverless worker queue function for topic 'myqueue'
app.worker('myqueue', (req, res) => {
console.log('The queue item is', req.body.payload);
res.end(); // done with one item
});
// API route that adds to the worker queue
app.post('/addQueue', async (req, res) => {
const conn = await Datastore.open();
const jobId = await conn.enqueue('myqueue', { data: req.body });
res.status(201).json({ jobId });
});
export default app.init();
```
## Worker queue configuration
- Topic names are unique strings. E.g. `"sendEmails"`
- Queue data are accessed through the `request.body.payload` object
- Each topic is processed as a separate queue
- `QUEUE_WORKERS` environment variable manage number of parallel workers. E.g. `QUEUE_WORKERS=10`
- Each queue is stored in separate system collections prefixed with `_queue_{TOPIC}`. E.g. if the topic is `sendEmail` the queue would be named `_queue_sendEmails`
- Read more about the [JavaScript Queue API](queueapi)
:::tip
If you need more complex functionality, which uses worker queues, make sure to check out the **[Workflow API](workflow-api)**.
:::
---
## Getting started
*(Note: This content contains MDX/JSX code)*
This short tutorial will show you how to create codehooks projects and spaces using the command line interface (CLI). Check out the [concepts](concepts) page for a more in-depth introduction. To use the Studio to create projects, check out this [quickstart](/docs).
## Install the CLI
Install the Codehooks command line interface (CLI), this lets you fully manage your projects from the command line.
```bash
npm i -g codehooks
```
> You need [Node.js](https://nodejs.org) to install the CLI.
:::tip Use 'Dev containers' to set up your codehooks development environment
Our CLI requires use of the terminal window, which can sometimes be challenging especially for Windows users. Check out our blog post about [how to set up a dev container](/blog/simplify-codehooks-development-with-devcontainers) for simplified and more consistent codehooks.io development environment on all platforms.
:::
## Sign up and Log in
Next you need to sign up / log in to your account (it's totally free), in this example we use a Github account. Your browser will open and direct you a secure login page, and there you can return to the CLI - logged in.
```bash
coho login
```
Then choose login method, e.g. Github.
```bash
Select option below
β― Use Github
Use Google
Exit
```
## Create project
Lets go forward and create a new project on your account.
A project contains the source code for your serverless functions.
```bash
coho create myproject
```
Follow the guide to create a personal or team project type.
Finally change directory to the new project and install the Codehooks standard library.
```bash
cd myproject
```
The `coho create` command has now created a new project space and generated a unique name for the API, in this example it's named `myproject-2b39` and `dev`.
Your account will be the owner of the project. This can be changed later.
:::note Deploy code to an existing project
If you have an existing project you'd like to use, for example created with the Studio application, you can connect and deploy the code to that project using the [CLI command](/docs/cli#init) `coho init` instead of `coho create`.
:::
## Create a serverless JavaScript Codehook
First, in the project directory, install the Codehooks standard open source libraries [codehooks-js](https://www.npmjs.com/package/codehooks-js).
```bash
npm i codehooks-js
```
Next, start your favorite code editor and open the auto generated `index.js` in your project directory.
```js title="index.js"
/*
* Auto generated Codehooks (c) example
*/
import { app } from 'codehooks-js';
// test route for https://.api.codehooks.io/dev/
app.get('/', (req, res) => {
res.send('CRUD server ready');
});
// Use Crudlify to create a REST API for any database collection
app.crudlify();
// bind to serverless runtime
export default app.init();
```
Save the JavaScript file.
## TypeScript support
Codehooks supports TypeScript (version 5) with strong typing. Just rename the index.js file to index.ts, update the package.json main property, and your're ready to go.
The code example below shows the initial code example using TypeScript.
```ts title="index.ts"
/* TypeScript */
import { app, httpRequest, httpResponse } from 'codehooks-js';
// strong typing for request and response
app.get('/', (req: httpRequest, res: httpResponse) => {
res.send('CRUD server ready');
});
// Use Crudlify to create a REST API for any database collection
app.crudlify();
// bind to serverless runtime
export default app.init();
```
```js title="package.json"
{
// rest of your package.json file
"main": "index.ts"
}
```
You are now ready to deploy your project.
## Deploy the code to the serverless cloud
```bash
coho deploy
```
Example output from the deploy command.
```bash
Deploying to Project: myproject-2b39 Space: dev
Deployed Codehook successfully! π
```
You can now test the [database CRUD REST API](/docs/databaserestapi.md) with a sample collection, for example `users`.
:::tip
Use the `coho info --examples` command to find your project name, API tokens and working curl examples.
:::
```bash title="curl shell command - POST a new user"
curl --location 'https://.api.codehooks.io/dev/users' \
--header 'x-apikey: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Bill",
"email": "william@example.com",
"active": true
}'
```
Example output from the curl test.
```js
{
"name": "Bill",
"email": "william@example.com",
"active": true,
"_id": "6421b3e6a3c762051688edf7"
}
```
Lets also test a query for the same data we've added.
```bash title="curl shell command - query users"
curl --location 'https://.api.codehooks.io/dev/users?name=Bill' \
--header 'x-apikey: ' \
--header 'Content-Type: application/json' \
```
Which returns an array of 1 object from the database.
```js
[
{
name: 'Bill',
email: 'william@example.com',
active: true,
_id: '6421b3e6a3c762051688edf7',
},
];
```
Our test shows that the automatic REST API is accessible on the `/dev/users` route, and we can successfully add and retrieve data from the database to the client.
:clap::clap:
---
## Real-time API
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
The Codehooks.io real-time API enables applications and clients to publish and subscribe to data events.
**API quick overview**
## State diagram
The following diagram shows the necessary dataflow between a client and a server.
```mermaid
sequenceDiagram
participant A as Client (listener)
participant B as Server (publisher)
A->>B: POST {metadata} to a '/channel'
B-->>A: Create a new ListenerID
A-->>B: Create a new EventSource(`/channel/${ListenerID}`)
B-->>A: Real-time Events
```
## realtime.createChannel(channel)
Create a real-time channel for clients listeners and and server publishers.
**Parameters**
- **channel**: string - e.g. '/goals'
**Returns**: void.
**Code example for creating a channel**
```js title="index.js"
import { app, realtime } from 'codehooks-js';
// create a real-time channel
realtime.createChannel('/goals');
/* more code here ... */
// bind to serverless runtime
export default app.init();
```
## realtime.publishEvent(channel,data, [query])
Publish a data event on a channel to all or some (query) listeners.
**Parameters**
- **channel**: string - e.g. '/goals'
- **data**: object - Event object to publish
- **query**: object - optional matching criteria for listeners to get the event
**Returns**: Promise: object
**Code example for publish events**
```js title="index.js"
import { app, realtime } from 'codehooks-js';
// create a real-time channel
realtime.createChannel('/goals');
// broadcast event
async function someFunction() {
const listeners = { topic: 'score' }; // send to all listeners subscribing to topic
const event = { 'team A': 0, 'team B': 1, player: 'Messi' };
const data = await realtime.publishEvent('/goals', event, listeners);
}
/* more logic here ...*/
// bind to serverless runtime
export default app.init();
```
## realtime.createListener(channel, data)
Create a new listener (ID) that clients can use to subscribe to an EventSource channel.
**Parameters**
- **channel**: string - Channel name, e.g. '/goals'
- **data**: object - Listener topics of interest, e.g. `{topic: "score"}`
**Returns**: Promise: listenerData with a unique `_id`
**Code example for creating a new listener**
```js title="index.js"
import { app, realtime } from 'codehooks-js';
// create a real-time channel
realtime.createChannel('/clock');
// client rest api to connect to the real time channel
app.post('/connect', async (req, res) => {
const listenerData = await realtime.createListener('/clock', req.body);
// return listener ID to client
res.json({ listenerID: listenerData._id });
});
app.job('* * * * *', async (req, res) => {
const listeners = { topic: 'tick' }; // send to all listeners subscribing to topic
const event = { 'The time is': new Date() };
const data = await realtime.publishEvent('/clock', event, listeners);
});
// bind to serverless runtime
export default app.init();
```
## realtime.getListener(channel, listenerID)
Get a listener object for a specific listener (ID) on a channel.
**Parameters**
- **channel**: string - Channel name, e.g. '/goals'
- **listenerID**: string - A valid \_id for an existing listener
**Returns**: Promise: listenerData
## realtime.getListeners(channel)
Get all listeners on a channel (channel).
**Parameters**
- **channel**: string - Channel name, e.g. '/goals'
**Returns**: Promise: array of listenerData
## realtime.removeListener(channel, listenerID)
Remove a listener (listenerID) from a channel (channel).
**Parameters**
- **channel**: string - Channel name, e.g. '/goals'
- **listenerID**: string - A valid \_id for an existing listener
**Returns**: Promise: listenerData
## Complete code example - Real-time chat
To better understand how the Codehooks.io real-time API works, check out the following code example. The complete source code, including install instructions, is avaliable at [Github](https://github.com/RestDB/codehooks-io-examples/tree/main/realtime-chat).
The code example is a simple web (plain vanilla ugly HTML) chat app for POST'ing messages to a REST API endpoint. The messages are then received on the client in real-time from the server. The server (Codehooks.io) uses the real-time API to subscribe and publish data to/from client requests.

[You can test out a live example here](https://vivacious-kingdom-f532.codehooks.io/public/index.html)
### HTML client page
```html title="/public/index.html"
Codehooks.io - Chat App
Codehooks.io - Chat example
Real-time status:
Initializing
```
### HTML client JavaScript
```js title="/public/chat.js"
const API_TOKEN = '0a2249d4-5f10-489c-8229-1f060ad1e0f6';
let listenerID = null;
document.addEventListener('DOMContentLoaded', function () {
const aliasInput = document.getElementById('aliasInput');
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');
// Function to send a message to the server
function sendMessage(message) {
fetch('/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-apikey': API_TOKEN,
},
body: JSON.stringify({
message: message,
listenerID,
alias: aliasInput.value.trim(),
}),
})
.then((response) => response.json())
.then((data) => {
console.log('Message sent:', data);
messageInput.value = ''; // Clear input after sending
messageInput.focus(); // Keep focus on input field
})
.catch((error) => {
console.error('Error:', error);
});
}
sendButton.addEventListener('click', function () {
if (messageInput.value) {
sendMessage(messageInput.value);
}
});
messageInput.addEventListener('keydown', function (event) {
// Check if Enter key is pressed without Shift key
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault(); // Prevent default to avoid line breaks or form submission
if (messageInput.value) {
sendMessage(messageInput.value);
}
}
});
// init real-time connection
startListener();
});
function addMessage(message) {
const messagesDiv = document.getElementById('messages');
const messageElement = document.createElement('div');
messageElement.textContent = message;
messagesDiv.appendChild(messageElement); // Adds the message at the bottom
// Auto-scroll to the bottom
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// connect to realtime SSE
async function startListener() {
// setup the real time stuff
const statusIndicator = document.getElementById('statusIndicator');
const interests = {}; // everything
var requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-apikey': API_TOKEN,
},
body: JSON.stringify(interests),
};
// get a real time listenerID
console.log('Connect request', requestOptions);
const response = await fetch('/connect', requestOptions);
const result = await response.json();
listenerID = result.listenerID;
console.log('GOT clientID', result);
// connect to reatime channel
let eventSource = new EventSourcePolyfill(`/chat/${result.listenerID}`, {
headers: {
'x-apikey': API_TOKEN,
},
});
statusIndicator.textContent = 'Connected';
statusIndicator.style.color = 'green';
// incoming message event
eventSource.onmessage = function (event) {
console.log('Event', event.data);
const result = JSON.parse(event.data);
addMessage(result.message);
};
// here we go
eventSource.onopen = function (event) {
// Connection is open
statusIndicator.textContent = 'Live data ready';
statusIndicator.style.color = 'green';
};
// oops, reconnecting if possible
eventSource.onerror = function (event) {
console.log('Error', event);
// An error occurred or the connection was closed
if (eventSource.readyState == EventSource.CLOSED) {
console.log('Connection was closed.');
}
statusIndicator.textContent = 'No connection';
statusIndicator.style.color = 'red';
};
return result;
}
```
### Codehooks.io Server
See install and deploy docs at [Github](https://github.com/RestDB/codehooks-io-examples/tree/main/realtime-chat).
```js title="index.js"
// Codehooks.io - Chat example
import { app, realtime } from 'codehooks-js';
// create a real-time channel
realtime.createChannel('/chat');
// client connects for a new real-time listener ID
app.post('/connect', async (req, res) => {
const listenerData = await realtime.createListener('/chat');
// return listener ID to client
res.json({ listenerID: listenerData._id });
});
// client post a new message
app.post('/messages', async (req, res) => {
console.log('Message in', req.body);
const { message, listenerID, alias } = req.body;
const data = await realtime.publishEvent('/chat', {
message: `${alias || 'Anonymous'}: ${message}`,
});
res.end(data);
});
// annoying message from a cron job every second minute ;)
app.job('*/2 * * * *', async (_, job) => {
const message = `Hello from cron job at ${new Date().toISOString()}`;
const data = await realtime.publishEvent('/chat', {
message: `cron: ${message}`,
});
job.end();
});
app.static({ route: '/public', directory: '/public' });
// bind to serverless runtime
export default app.init();
```
---
## REST API Routing
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
App routes lets you create secure public [(REST) API](https://en.wikipedia.org/wiki/Representational_state_transfer) endpoints to serverless JavaScript functions in your application.
## Example REST API route
The example app routes below creates two REST API endpoint routes that triggers a serverless function. This simple example function `echo` does nothing useful, other than echoing back the request data to the client as JSON.
```js title="index.js"
import app from 'codehooks-js';
// Codehooks API routes
app.post('/myroute', echo);
app.get('/myroute/:var1/:var2', echo);
// Serverless function
function echo(req, res) {
res.json(req);
}
// bind to Codehooks runtime
export default app.init();
```
After deploying the REST API with the [`coho deploy` CLI command](/docs/cli#deploy) we can run a quick test from the command line using `curl`.
:::tip
You can use [Postman](https://www.postman.com/api-platform/api-testing/) together with Codehooks for testing your APIs.
:::
```bash title="Example command line"
curl https://myproject-abcd.api.codehooks.io/dev/myroute/123/321
-H 'x-apikey: 6e111ea6-6b3a-412d-8f6b-061c816c67c9'
```
The example shows that our function echoes back the input as a structured (and very handy) JSON object.
```js title="Example output"
{
"hostname": "myproject-abcd.api.codehooks.io",
"headers": {
"host": "myproject-abcd.api.codehooks.io",
"user-agent": "curl/7.77.0",
"accept": "*/*",
"x-apikey": "6e111ea6-6b3a-412d-8f6b-061c816c67c8"
},
"query": {},
"path": "/dev/myroute/123/321",
"apiPath": "/myroute/123/321",
"originalUrl": "/dev/myroute/123/321",
"params": {
"var1": "123",
"var2": "321"
},
"body": {},
"method": "GET"
}
```
After deployment the specified routes will be available as HTTPS endpoints.
## REST API endpoint URL spec
A REST API app route is available at an HTTPS endpoint based on the HTTP verb, tenant ID, datastore name and API route specification.
```shell
ββββ Project Id, e.g. booking-2b39
β ββββ Project space name (environment) , e.g. prod
β β ββββ API route, e.g. /cars/:model/:make
β β β ββββ Parameters, e.g. color=black
β β β β
β β β β
β β β β
β β β β
https://.api.codehooks.io//[?QUERY]
Example URL: /cars/:model/:make
https://booking-2b39.api.codehooks.io/prod/cars/Tesla/model-x?color=black&campaign=A
```
## REST API route matching
Any request agaist a space REST API, e.g. `/dev/*`, are matched against the deployed and declared app route functions.
### String path
Route declaration must match exact to trigger function.
```js
// matches: https://apollo-ff00.api.codehooks.io/dev/admin
app.get('/admin', myFunc);
```
### Path parameters
Route declaration can match variable named segments of route to trigger function.
```js
// matches: https://apollo-ff00.api.codehooks.io/dev/admin/abc-123
app.get('/admin/:messageId', myFunc);
```
### Wildcard
Route declaration can match wildcard segments of route to trigger function.
```js
// matches: https://apollo-ff00.api.codehooks.io/dev/user/abc-123/inbox
// matches: https://apollo-ff00.api.codehooks.io/dev/user/turbo-xyz/inbox
app.get('/user/*/inbox', myFunc);
```
### Regular expression
Route declaration matches a regular expression to trigger function.
```js
// matches: https://apollo-ff00.api.codehooks.io/dev/foo/bar
// matches: https://apollo-ff00.api.codehooks.io/dev/user/bar/foo/y/x
app.get(/^\/(foo|bar)/, myFunc);
```
## The REST API request object
The request object contains these following properties for a client (HTTPS) request.
### Overview
node.value.startsWith('request.'))}/>
### request.headers
Get the HTTP header values as key-value pairs.
```js
console.log(req.headers['user-agent']); // -> PostmanRuntime/7.29.0
```
### request.query
Get the URL query parameters as key-value pairs.
```js
// route: /product
// url : /dev/product?price=10
console.log(req.query.price); // -> 10
```
### request.params
Get the URL route variables as key-value pairs.
```js
// route: /order/:orderID/:type
// url : /dev/order/12345/small
console.log(req.params.orderID, req.params.type); // -> 12345, small
```
### request.body
Get the request body.
```js
console.log(req.body); // -> {"foo": "bar"}
```
### request.path
Get the URL full path.
```js
// route: /order/:orderID
// url : /dev/order/12345
console.log(req.path); // -> /dev/order/12345
```
### request.apipath
Get the URL api path.
```js
// route: /order/:orderID
// url : /dev/order/12345
console.log(req.apipath); // -> /order/12345
```
### request.originalUrl
Get the URL full path and query parameter string.
```js
// route: /order/:orderID
// url : /dev/product?size=small
console.log(req.originalUrl); // -> /dev/product?size=small
```
### request.method
Get the HTTP request verb.
```js
// url: /dev/product
console.log(req.method); // -> GET
```
### request.hostname
Get the project URL domain name.
```js
// url: https://myproject-ff00.api.codehooks.io/dev/product
console.log(req.hostname); // -> myproject-ff00.api.codehooks.io
```
### request.pipe(Stream)
Process a request data stream by piping it to another stream. Useful to process other data formats than JSON, e.g. binary or raw data content types - `content-type: application/octet-stream`.
```js
const mystream = new PassThrough();
// process mystream
req.pipe(mystream);
```
### request.on(event, data)
Process a request data stream by listening to the request data emitter. Useful to process other data formats than JSON, e.g. binary or raw data content types - `content-type: application/octet-stream`.
```js
req.on('data', (buf) => {
console.log(buf.length); // process buf data
});
req.on('end', (bytes) => {
console.log('Got binary data', bytes);
res.end('Got binary ' + bytes);
});
```
## The REST API response object
The response object sends data back to the client and finalizes the request.
### Overview
node.value.startsWith('response.'))}/>
### response.set(header, value)
Set a response header value.
**Parameters**
- `header` **string**, [read more about HTTP header values](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)
- `value` **string**
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.set('Content-Type', 'text/html; charset=UTF-8'); // set a response header
res.end('
Hello world!
'); // send html back to client
}
```
### response.headers(Object)
Set multiple response header values.
**Parameters**
- `Object` **JSON**, [read more about HTTP header values](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.headers({
'Content-Type': 'text/html; charset=UTF-8',
'X-myheader': '123456890',
});
res.send('
Hello world!
'); // send html back to client
}
```
### response.status(HttpStatusCode)
Return a HTTP response status code for a client request.
**Parameters**
- `HttpStatusCode` **integer**, [read more about HTTP status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.status(401); // Unauthorized
res.end();
}
```
### response.json(Object)
Return a JSON response for a client request.
:::caution
Ends the client request and terminates the Codehook execution.
:::
**Parameters**
- `Object` **JSON**
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.json({ message: 'Hello' });
}
```
### response.write(data, [encoding])
Stream data back to the client as text or binary data buffers.
NB! `content-type` must be set before any `write` operations.
**Parameters**
- `data` **string** or any **data** that the Content-Type allows
- `encoding` **optional string** use `res.write(mybuf, 'buffer')` to write binary data back to client
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.set('content-type', 'text/plain');
res.write('Hello');
res.write('world!');
res.write('\n');
res.end();
}
```
### response.send(data)
End response for a client request.
:::caution
Ends client request and terminates the Codehook execution immediately.
If not called, the Codehook will not return and finally time out.
:::
**Parameters**
- `data` **string**
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.send('Thanks for visiting!');
res.end();
}
```
### response.redirect(statusCode, URL)
Redirect a client request.
**Parameters**
- `statusCode` **number** 301 or 302
- `URL` **string** A valid relative or absolute URL
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.redirect(301, 'https://example.com');
}
```
### response.end()
End response for a client request.
:::caution
Ends client request and terminates the Codehook execution immediately.
If not called, the Codehook will not return and finally time out.
:::
**Parameters**
- none
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.send('Thanks for visiting!');
res.end();
}
```
### response.writable
Get the Writable object that can be used in node.js Stream operations.
```js
mystream.pipe(res.writable);
```
## Middleware functions
You can create middleware logic by chaining functions in an array or globally with the `app.use` function.
Each function have a `next` method parameter that is used to "chain" the next function or to terminate the chain. The example below shows how a middleware function is called before the main function. The middelware function typically check some state or logic before proceeding to the next.
```js title="index.js"
import app from 'codehooks-js';
// Global middleware on all routes
app.use((req, res, next) => {
console.log("I'm global");
res.next();
});
// Global middleware on route matches
app.use('/my*', (req, res, next) => {
console.log("I'm global match for /my*");
res.next();
});
// Example route with middleware
app.post('/myroute', middleWare, myFunc);
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
// All is OK, got here finally
res.end();
}
function middleWare(req, res, next) {
let OK = true;
// do something here
if (OK) {
next();
} else {
res.status(401); // Unauthorized
res.end();
}
}
export default app.init();
```
## Wildcard routes
Wildcards lets you match all `methods`and all `routes` that are not matched by previous `routes`.
See example code below.
```js {7} title="index.js"
import app from 'codehooks-js'
app.get('/myroute': getFunc);
app.put('/myroute/:id': putFunc);
// wildcard route
app.all('/*': (req, res) => {
res.end("I catch all methods, on all routes not matched before");
}
)
export default app.init();
```
---
## Application secrets
Application secrets are stored as environment variables in your application space.
To protect your private secrets, environment variables can be encrypted and then dynamically added to your application serverless runtime.
A Secret is typically an encrypted environment variable that contains a small amount of sensitive data such as a password, a token, or a key.
## Adding secret environment variables
The following Codehooks [CLI command](/docs/cli#set-env) will create an encrypted enviroment variable that you can use in your code as `process.env.MY_SECRET_VARIABLE`:
```bash
coho set-env MY_SECRET_VARIABLE 'some secret content here' --encrypted
```
## Adding regular non-secret environment variables
Running the `set-env` command without the `--encrypted` parameter will create a plain text variable, which you can use in your code as `process.env.MY_VARIABLE`.
The follwing Codehooks CLI command will create an encrypted environment variable that you can use in your code as `process.env.VARIABLENAME`:
```bash
coho set-env MY_VARIABLE 'some regular content here'
```
## Inspecting secrets and environment variables
You can use the `coho info` CLI command to inspect your system variables. You will not be able to inspect the actual content of encrypted variables. Example output of the `info` command is shown below:
```bash
coho info
...
Spaces:
ββββββββββββββββ¬ββββββββββ¬ββββββββββββββββ¬βββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β Name β Tokens β Allowed Hosts β Jwks β Env β
ββββββββββββββββΌββββββββββΌββββββββββββββββΌβββββββΌββββββββββββββββββββββββββββββββββββββββ€
β dev (active) β β β β MY_SECRET_VARIABLE=(encrypted) β
β β β β β MY_VARIABLE=some regular content here β
ββββββββββββββββ΄ββββββββββ΄ββββββββββββββββ΄βββββββ΄ββββββββββββββββββββββββββββββββββββββββ
...
```
## Removing environment variables
The follwing Codehooks CLI command will remove any enviroment variable:
```bash
coho remove-env MY_VARIABLE
```
:::tip
You can also set these environment variables using the account UI.
:::
---
## Workflow API - Build Reliable Stateful Workflows with durable functions
*(Note: This content contains MDX/JSX code)*
Create and deploy robust, scalable workflows using queued functions and state management. Build reliable backend systems with automatic retry, state persistence, and distributed processing.
:::info
The Workflow API is an early access release. We appreciate any feedback from developers using it.
The Workflow API is available from [`codehooks-js`](https://www.npmjs.com/package/codehooks-js) version `1.3.12`.
:::
## Overview
Codehooks workflows combines persistent queues with state management to create reliable, scalable workflows. Each step in a workflow can modify the process state and control the flow using the `goto` function, enabling complex workflows with branching, loops, and conditional execution. The system automatically handles state persistence, distributed processing, and error recovery.
### Key Features
- **State Management**
- Persistent state storage
- Atomic state updates
- Complete execution history
- State inspection and debugging
- **Queue-Based Processing**
- Queued (durable) functions
- Automatic retry on failures
- Load balancing across workers
- No lost messages or duplicate processing
- Parallel execution of workflow steps
- **Scalability & Reliability**
- Distributed processing
- Automatic failover
- State recovery
- Error handling and monitoring
### Why Use Workflows?
Workflows help you build reliable backend systems and integration logic. Using queued functions they handle system failures and network issues by automatically resuming from the last known state after crashes or connection losses. This makes them ideal for critical processes like order processing and payment flows. The system distributes work across multiple workers and processes long-running tasks asynchronously. As a developer, you can focus on implementing business logic while the framework handles state persistence, retries, and scaling.
## A simple workflow example
Let's create a simple workflow app that demonstrates branching based on a random number. The workflow will process even and odd numbers differently.
```mermaid
graph TD
A[begin] -->|initialize| B[decide]
B -->|even| C[evenPath]
B -->|odd| D[oddPath]
C -->|complete| E[end]
D -->|complete| E
E -->|finish| F[Exit]
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#ffd,stroke:#333,stroke-width:2px
style C fill:#dff,stroke:#333,stroke-width:2px
style D fill:#dff,stroke:#333,stroke-width:2px
style E fill:#dfd,stroke:#333,stroke-width:2px
style F fill:#9f9,stroke:#333,stroke-width:2px
```
The full source code for the workflow steps is shown below.
```javascript title="index.js"
import { app } from 'codehooks-js';
// Create a workflow definition
const workflow = app.createWorkflow('simpleTask', 'Basic workflow example', {
// Step 1: Initialize the workflow
begin: async function (state, goto) {
state = {
message: 'Starting workflow',
// Add a random number to demonstrate branching
value: Math.floor(Math.random() * 10),
};
goto('decide', state);
},
// Step 2: Decide which path to take
decide: async function (state, goto) {
// Branch based on whether the value is even or odd
if (state.value % 2 === 0) {
goto('evenPath', state);
} else {
goto('oddPath', state);
}
},
// Step 3a: Handle even numbers
evenPath: async function (state, goto) {
state = {
message: 'Processing even number',
path: 'even',
processed: true,
};
goto('end', state);
},
// Step 3b: Handle odd numbers
oddPath: async function (state, goto) {
state = {
message: 'Processing odd number',
path: 'odd',
processed: true,
};
goto('end', state);
},
// Step 4: End the workflow
end: function (state, goto) {
state = {
final_message: `Workflow completed! Processed ${state.path} number: ${state.value}`,
};
goto(null, state); // workflow complete
},
});
// emitted event when a workflow completes
workflow.on('completed', (data) => {
console.log('Workflow completed:', data);
});
// REST API to start a new workflow instance
app.post('/start', async (req, res) => {
const result = await workflow.start({ foo: 'bar' });
res.json(result);
});
// export app interface to serverless execution
export default app.init();
```
You can [deploy](/docs/quickstart-cli#deploy-the-code-to-the-serverless-cloud) the workflow in the [Studio](/blog/how-to-use-chatgpt-build-nodejs-backend-api-codehooks#using-the-web-based-codehooks-studio-for-development) app directly or with the [CLI command `coho deploy`](/docs/cli#deploy).
See more [examples](#example-workflows) at end of this page.
## Workflow State Storage and Inspection
All workflow instances and their states are automatically persisted in a collection (default name: `workflowdata`). This collection stores the complete state of every workflow instance, making it easy to inspect and debug workflows at any time.
Each document in the collection contains:
- `_id`: Unique identifier for the workflow instance
- `nextStep`: The next step to be executed (null when workflow is complete)
- `createdAt`: Timestamp when the workflow was created
- `updatedAt`: Timestamp of the last state update
- `workflowName`: Name of the workflow definition
- `previousStep`: Name of the last executed step
- `stepCount`: Object containing execution statistics for each step
- `visits`: Number of times the step was executed
- `startTime`: When the step started execution
- `finishTime`: When the step finished execution
- `totalTime`: Total execution time in milliseconds
- `totalTime`: Total workflow execution time in milliseconds
- `...state`: Your custom state properties for the workflow
**An example state object** for a completed `simpleTask` workflow is shown below:
```js
{
"foo": "bar",
"nextStep": null,
"createdAt": "2025-06-09T15:35:41.318Z",
"workflowName": "simpleTask",
"stepCount": {
"begin": {
"visits": 1,
"startTime": "2025-06-09T15:35:41.529Z",
"totalTime": 5,
"finishTime": "2025-06-09T15:35:41.534Z"
},
"decide": {
"visits": 1,
"startTime": "2025-06-09T15:35:41.700Z",
"totalTime": 7,
"finishTime": "2025-06-09T15:35:41.707Z"
},
"evenPath": {
"visits": 1,
"startTime": "2025-06-09T15:35:41.757Z",
"totalTime": 5,
"finishTime": "2025-06-09T15:35:41.762Z"
},
"end": {
"visits": 1,
"startTime": "2025-06-09T15:35:42.578Z",
"totalTime": 8,
"finishTime": "2025-06-09T15:35:42.586Z"
}
},
"_id": "6846ff4d5a5945501a11d691",
"updatedAt": "2025-06-09T15:35:42.586Z",
"message": "Processing even number",
"value": 6,
"previousStep": "end",
"path": "even",
"processed": true,
"final_message": "Workflow completed! Processed even number: 6",
"totalTime": 1260
}
```
You can query this collection directly to inspect all workflow states:
```js
// Example REST API to get all completed instances of a workflow
app.get('/some', async (req, res) => {
// Example: Find all running instances of a workflow
const db = await Datastore.open();
const runningInstances = await db
.getMany('workflowdata', {
workflowName: 'simpleTask',
nextStep: null,
})
.toArray();
res.json(runningInstances);
});
```
Or you can query the collection for a specific workflow instance:
```js
// Example REST API to get a specific workflow instance
app.get('/one/:id', async (req, res) => {
const db = await Datastore.open();
// Example: Get state of a specific workflow instance
const instance = await db.getOne('workflowdata', {
_id: req.params.id,
});
res.json(instance);
});
```
To use a different collection name, configure it when creating the workflow:
```js
const workflow = app.createWorkflow('myWorkflow', 'description', steps);
workflow.configure({ collectionName: 'my_custom_collection' });
```
Here's how the workflow data appears in Codehooks Studio for an example workflow:

The screenshot shows the state of a workflow instance that:
1. Started at 17:21:00
2. Branched trough the `even` path
3. Is completed because nextStep is `null`
4. Contains the state data with the message properties
## Core API Overview
**Methods**
The workflow API provides these core methods for managing persistent workflows:
### createWorkflow(name, description, steps)
Creates a new workflow of persistent steps.
```js
const workflow = app.createWorkflow('workflow', 'description', {
START: async (state, goto) => {
// step implementation
},
});
```
**Parameters:**
- `name` (string): Unique identifier for the workflow
- `description` (string): Human-readable description of the workflow
- `steps` (WorkflowDefinition): Object containing step definitions
**Returns:** Workflow instance for managing the workflow
**Parameters:**
- `name` (string): Unique identifier for the workflow
- `description` (string): Human-readable description
- `steps` (WorkflowDefinition): Step definitions
**Returns:** Promise with the registered workflow name
### start(initialState)
Starts a new Workflow instance.
```js
const result = await workflow.start({ data: 'value' });
// Returns state, e.g. {_id: '682ec2b3dab9887c1da726e9', ...state}
```
**Parameters:**
- `initialState` (any): Initial state for the workflow instance
**Returns:** Promise with the workflow instance
### updateState(instanceId, state, options?)
Updates the state of a workflow instance.
```js
await workflow.updateState(
'682ec2b3dab9887c1da726e9',
{ count: 1 },
{ continue: true }
);
```
**Parameters:**
- `instanceId` (string): ID of the workflow instance
- `state` (any): New state to update with
- `options` (UpdateOptions, optional): Options for the update, `{ continue: false }` to avoid continuing the step
**Returns:** Promise with the updated state
### setState(instanceId, stateData)
Set the complete state of a workflow instance.
```js
await workflow.setState('682ec2b3dab9887c1da726e9', {
_id: '682ec2b3dab9887c1da726e9',
state: { count: 1, status: 'active' },
});
```
**Parameters:**
- `instanceId` (string): ID of the workflow instance
- `stateData` (object): Object containing `_id` and `state`
**Returns:** Promise that resolves to void
### continue(instanceId, reset?)
Continue a paused workflow instance.
```js
const result = await workflow.continue('682ec2b3dab9887c1da726e9', false);
// Returns: { instanceId: string }
```
**Parameters:**
- `instanceId` (string): ID of the workflow instance
- `reset` (boolean, optional): Whether to reset all step counts (true) or just the current step (false)
**Returns:** Promise with the queue ID for the continued step
### getWorkflowStatus(id)
Get the status of a workflow instance.
```js
const status = await workflow.getWorkflowStatus('682ec2b3dab9887c1da726e9');
```
**Parameters:**
- `id` (string): ID of the workflow instance
**Returns:** Promise with the workflow status
### getInstances(filter)
Lists workflow instances matching a filter.
```js
const instances = await workflow.getInstances({ status: 'running' });
```
**Parameters:**
- `filter` (any): Filter criteria for workflows
**Returns:** Promise with list of workflow instances
### cancelWorkflow(id)
Cancel a workflow instance.
```js
const result = await workflow.cancelWorkflow('682ec2b3dab9887c1da726e9');
```
**Parameters:**
- `id` (string): ID of the workflow instance to cancel
**Returns:** Promise with the cancellation result
### continueAllTimedOut()
Continues all timed out step instances in the workflow. This is useful for recovering workflows that have timed out due to system issues or long-running operations.
**Returns:** Promise with array of results containing instance IDs for continued workflows
**Example:**
```js title="Continue timed out workflows"
// Create workflow with 5 minute timeout
const workflow = app.createWorkflow('simpleTask', 'Basic workflow example', {
/* workflow steps */
});
workflow.configure({
timeout: 1000 * 60 * 5, // 5 minute timeout threshold
});
// Schedule job to continue timed out workflows every 5 minutes
app.job('*/5 * * * *', async (req, res) => {
const result = await workflow.continueAllTimedOut();
if (result.length > 0) {
console.log('Continued these dangling workflows:', result);
}
res.end();
});
```
### findTimedOutSteps(filter?)
Find all workflow instances with timed out steps.
```js
const timedOutWorkflows = await workflow.findTimedOutSteps();
// Returns array of workflows with timeout details:
// [{
// workflowId: string,
// workflowName: string,
// isTimedOut: true,
// executionTime?: number,
// runningTime?: number,
// timeout: number,
// step: string,
// startTime: string,
// finishTime?: string,
// currentTime?: string,
// timeoutSource: string
// }]
```
**Parameters:**
- `filter` (any, optional): Optional filter criteria for workflows
**Returns:** Promise with array of workflow instances that have timed out steps
### on(event, listener)
Registers an event listener.
```js
workflow.on('stepStarted', ({ workflowName, step, state, instanceId }) => {
console.log(`Step ${step} started in workflow ${workflowName}`);
});
```
**Parameters:**
- `event` (WorkflowEvent): Name of the event to listen for
- `listener` (function): Callback function to handle the event
**Returns:** workflow instance
### once(event, listener)
Register a one-time event listener.
```js
workflow.once('completed', (data) =>
console.log('Workflow completed once:', data)
);
```
**Parameters:**
- `event` (WorkflowEvent): Name of the event to listen for
- `listener` (function): Callback function to handle the event
**Returns:** workflow instance
### off(event, listener)
Remove an event listener.
```js
workflow.off('stepStarted', myListener);
```
**Parameters:**
- `event` (WorkflowEvent): Name of the event
- `listener` (function): Callback function to remove
**Returns:** workflow instance
### emit(event, data)
Emit an event.
```js
workflow.emit('customEvent', { message: 'Custom event data' });
```
**Parameters:**
- `event` (WorkflowEvent): Name of the event to emit
- `data` (WorkflowEventData): Event data
**Returns:** boolean indicating if the event was handled
### configure(options)
Configure the workflow engine.
```js
import { app } from 'codehooks-js';
const workflow = app.createWorkflow('myWorkflow', 'My workflow description', {
// ... workflow steps ...
});
// Configure workflow settings
workflow.configure({
collectionName: 'workflows', // Set storage collection name
queuePrefix: 'workflow', // Set queue prefix name
timeout: 30000, // Global timeout in milliseconds
maxStepCount: 3, // Maximum step execution count
steps: {
// Step-specific configuration
stepName: {
timeout: 3000, // Step-specific timeout
maxRetries: 3, // Step-specific retry count
},
},
});
```
The configuration object supports these options:
- `collectionName` (string, optional): Collection name for storing workflow data. Default: `'workflowdata'`
- `queuePrefix` (string, optional): Queue prefix for workflow jobs. Default: `'workflowqueue'`
- `timeout` (number, optional): Global timeout in milliseconds for workflow steps. Default: `30000`
- `maxStepCount` (number, optional): Maximum number of times a step can be executed. Default: `3`
- `steps` (object, optional): Step-specific configuration options
- `timeout` (number, optional): Timeout in milliseconds for this specific step
- `maxRetries` (number, optional): Maximum number of retries for this step
**Returns:** void
## Events
The workflow API emits these events:
### workflowCreated
Emitted when a new workflow is registered.
```js
workflow.on('workflowCreated', ({ name, description }) => {
console.log(`Workflow "${name}" registered: ${description}`);
});
```
**Event Data:**
- `name` (string): Workflow name
- `description` (string): Workflow description
### workflowStarted
Emitted when a new workflow instance is started.
```js
workflow.on('workflowStarted', ({ name, initialState }) => {
console.log(`Workflow "${name}" started with initial state:`, initialState);
});
```
**Event Data:**
- `name` (string): Workflow name
- `initialState` (any): Initial state data
### stepStarted
Emitted when a step begins execution.
```js
workflow.on('stepStarted', ({ workflowName, step, state, instanceId }) => {
console.log(`Step "${step}" started in workflow "${workflowName}"`);
});
```
**Event Data:**
- `workflowName` (string): Name of the workflow
- `step` (string): Name of the step
- `state` (any): Current workflow state
- `instanceId` (string): Workflow instance ID
### stateUpdated
Emitted when a step's state is updated.
```js
workflow.on('stateUpdated', ({ workflowName, state, instanceId }) => {
console.log(`State updated in workflow "${workflowName}":`, state);
});
```
**Event Data:**
- `workflowName` (string): Name of the workflow
- `state` (any): Updated state data
- `instanceId` (string): Workflow instance ID
### stepWaiting
Emitted when a step is waiting for input.
```js
workflow.on('stepWaiting', ({ workflowName, step, instanceId }) => {
console.log(`Step "${step}" waiting for input in workflow "${workflowName}"`);
});
```
**Event Data:**
- `workflowName` (string): Name of the workflow
- `step` (string): Name of the step
- `instanceId` (string): Workflow instance ID
### workflowContinued
Emitted when a workflow instance is continued after waiting.
```js
workflow.on('workflowContinued', ({ workflowName, step, instanceId }) => {
console.log(`Workflow "${workflowName}" continued from step "${step}"`);
});
```
**Event Data:**
- `workflowName` (string): Name of the workflow
- `step` (string): Name of the step
- `instanceId` (string): Workflow instance ID
### stepEnqueued
Emitted when a step function is added to queue.
```js
workflow.on('stepEnqueued', ({ workflowName, step, instanceId }) => {
console.log(`Step "${step}" enqueued for workflow "${workflowName}"`);
});
```
**Event Data:**
- `workflowName` (string): Name of the workflow
- `step` (string): Name of the step
- `instanceId` (string): Workflow instance ID
### completed
Emitted when a workflow instance is completed.
```js
workflow.on('completed', ({ message, state }) => {
console.log('Workflow completed:', message, state);
});
```
**Event Data:**
- `message` (string): Completion message
- `state` (any): Final workflow state
### cancelled
Emitted when a workflow instance is cancelled.
```js
workflow.on('cancelled', ({ id }) => {
console.log(`Workflow instance "${id}" was cancelled`);
});
```
**Event Data:**
- `id` (string): Workflow instance ID
### error
Emitted when an error occurs.
```js
workflow.on('error', ({ error }) => {
console.error('Workflow error:', error);
});
```
**Event Data:**
- `error` (Error): Error object with details
## Step Function
Each step function receives three parameters:
### Parameters
- `state` (any): Current workflow state object that can be modified and passed to next step
- `goto` (function): Function to transition between steps, supports:
- Name of next step to transfer to: `goto('stepName', state)`
- Parallel execution of multiple steps: `goto(['p1', 'p2'], state)`.
- All parallel steps must join in the same next step, i.e. `goto('samestep', state)`.
- Process will wait for all parallel steps to finish before continuing to `samestep`.
- If name of next step is `null` the workflow is completed: `goto(null, state)`
- Optional third parameter for additional options: `goto('nextStep', state, options)`
- `wait` (function, optional): Function to wait for an external state change
- optional state update before going into a wait state, e.g. `wait()` or `wait({val: 42})`
- Call [updateState](#updatestateinstanceid-state-options) to continue workflow
### Examples
Basic step with single transition:
```js
async function step(state, goto) {
// Update state
state = { count: state.count + 1 };
// Transition to next step
goto('nextStep', state);
}
```
Step with conditional transitions:
```js
async function conditionalStep(state, goto) {
if (state.count >= 3) {
// End workflow when condition met
goto(null, state);
} else if (state.needsValidation) {
// Go to validation step
goto('validate', state);
} else {
// Continue to next step
goto('process', state);
}
}
```
Step with parallel execution:
```js
async function parallelStep(state, goto) {
// Execute multiple steps in parallel
goto(['processA', 'processB'], state);
}
// Both processA and processB must eventually call:
// goto('joinStep', state);
```
Step with a wait state:
```js {6}
async function stepWaiting(state, goto, wait) {
// goto bar or wait
if (state.foo === 'bar') {
goto('bar', state);
} else {
wait({ status: 'waiting for bar' });
}
}
```
## Error handling
Workflow errors are automatically captured and logged. You can monitor errors in several ways:
### Logging and Monitoring
- **Studio UI**: View workflow logs in real-time using the Studio interface
- **CLI**: Stream logs with `coho log -f` to see workflow execution and errors
- **Database**: Errors are stored in the workflow instance as `lastError` property
### Error Inspection
To find workflow instances with errors, use the Studio collection (`workflowdata`) query tool with these example queries:
```js
// Find all workflow instances with errors
{ lastError: { $exists: true } }
// Find all errors in a specific workflow
{ workflowName: 'simpleTask', lastError: { $exists: true } }
```
### Error Properties
The `lastError` object contains:
- `message`: Error message
- `updatedAt`: When the error occurred
### Example Error Object
```js
{
lastError: {
message: "Failed to process payment",
stack: "Error: Payment service unavailable\n at processPayment...",
step: "paymentStep",
timestamp: "2024-03-14T12:34:56.789Z"
}
}
```
### Best Practices
1. **State Management**
- Keep state objects simple and serializable
- Use atomic updates for state changes
- Validate state at each step
- Monitor state size and complexity
2. **Error Handling**
- Monitor errors in logs and database
- Use try/catch in steps that might fail
- Set appropriate timeouts
- Implement retry logic for transient failures
3. **Scalability**
- Design stateless step functions
- Use appropriate queue configurations
- Monitor worker performance
- Implement proper error recovery
4. **Monitoring**
- Track workflow completion rates
- Monitor error frequencies
- Watch queue lengths
- Set up alerts for critical failures
## Example Workflows
Here are some example workflows demonstrating common patterns and use cases. Each example includes a visualization, explanation of the steps, and complete source code.
### Minimal workflow example
This minimal example demonstrates the basic structure of a workflow with just two steps. It shows how to:
- Create a simple workflow definition
- Set and modify workflow state
- Use `goto` to control flow
- Expose a REST endpoint to start the workflow
```mermaid
flowchart TD
A[POST /start] --> |Initial state|B[start step]
B --> C[end step]
C --> |End state|D[Workflow completed]
```
The code below implements this workflow, handling the state and transitions:
```js
import { app } from 'codehooks-js';
// The workflow definition
const workflow = app.createWorkflow('minimal', 'Minimal workflow example', {
start: (state, goto) => {
goto('end', { ...state, message: 'Hello World' });
},
end: (state, goto) => goto(null, state), // null complete the workflow
});
// A REST API to start a new workflow instance
app.post('/start', async (req, res) =>
res.json(await workflow.start({ prop: 'some value' }))
);
export default app.init();
```
### Customer onboarding
Here's a visualization of the customer onboarding workflow:
```mermaid
graph TD
A[START] -->|send welcome email| B[waitForValidation]
B -->|validated: false| B
B -->|validated: true| C[complete]
C -->|goto null = finish| D[End]
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#ffd,stroke:#333,stroke-width:2px
style C fill:#dfd,stroke:#333,stroke-width:2px
style D fill:#9f9,stroke:#333,stroke-width:2px
classDef waiting fill:#ffd,stroke:#333,stroke-width:2px
class B waiting
```
The workflow shows a process that:
1. Starts with customer email
2. Sends welcome email and enters validation state
3. Stays in validation state (no goto) until email is confirmed
4. Completes when email is validated (goto null = finish)
Here's a practical example of a customer onboarding workflow:
```javascript title="index.js"
import { app } from 'codehooks-js';
const workflow = app.createWorkflow(
'customerOnboarding',
'Customer signup and email validation',
{
START: async function (state, goto) {
// Initialize onboarding state
state = {
...state,
status: 'started',
email: state.email,
validated: false,
startedAt: new Date().toISOString(),
};
// Send welcome email
await sendWelcomeEmail(state.email);
// Move to validation step
goto('waitForValidation', state);
},
waitForValidation: async function (state, goto, wait) {
// Check if email was validated
if (state.validated) {
state.status = 'email_validated';
goto('complete', state);
} else {
// wait for validated = true
wait({ status: 'waiting_for_validation' });
}
},
complete: function (state, goto) {
// Finalize onboarding
state.status = 'completed';
state.completedAt = new Date().toISOString();
goto(null, state); // null === finish
},
}
);
// Event listeners for monitoring
workflow.on('completed', (data) => {
console.log('Onboarding completed for:', data);
});
workflow.on('stepStarted', (data) => {
console.log('Step started:', data.step, 'for customer:', data);
});
// API endpoints
app.post('/start-onboarding', async (req, res) => {
const { email } = req.body;
const result = await workflow.start({ email });
res.send(result);
});
// Endpoint to validate email (called by email webhook)
app.post('/validate-email', async (req, res) => {
const { email, token } = req.body;
// Find the onboarding instance for this email
const instances = await workflow.getInstances({
email: email,
validated: false,
});
if (instances.length > 0) {
const instance = instances[0];
// Update state with validation
const result = await workflow.updateState(
instance._id,
{
validated: true,
validatedAt: new Date().toISOString(),
validationToken: token,
},
{ continue: true } // this Step is complete
);
res.send(result);
} else {
res.status(404).send({ error: 'No pending onboarding found' });
}
});
// Helper function to send welcome email
async function sendWelcomeEmail(email) {
// Implementation of email sending
console.log('Sending welcome email to:', email);
// In a real app, you would use your email service here
}
export default app.init();
```
### Batch Example
This example demonstrates a batch process for managing long-running data operations. It's useful for scenarios like:
- Processing large datasets with multiple validation steps
- Orchestrating complex data transformations across systems
- Managing stateful operations that require error recovery
- Coordinating multi-step data synchronization tasks
- Implementing audit trails for data operations
- Handling distributed transactions with rollback capabilities
- Automating periodic data maintenance and cleanup tasks
- Building resilient ETL (Extract, Transform, Load) pipelines
The flow diagram below shows a three-step process that validates data against an external API, updates a database record, and transfers the result to an archive system. Each step includes error handling and state tracking.
```mermaid
graph TD
A[START] -->|init state| B[checkCustomerStatus]
B -->|check API| C[updateStatus]
C -->|update DB| D[transferData]
D -->|archive data| E[End]
B -->|error| F[error]
C -->|error| F
D -->|error| F
F -->|log error| E
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#dff,stroke:#333,stroke-width:2px
style C fill:#dff,stroke:#333,stroke-width:2px
style D fill:#dff,stroke:#333,stroke-width:2px
style F fill:#fdd,stroke:#333,stroke-width:2px
style E fill:#9f9,stroke:#333,stroke-width:2px
```
```javascript title="batch.js"
import { app } from 'codehooks-js';
const steps = app.createWorkflow(
'customerCleanup',
'Batch process customer data cleanup',
{
START: async function (state, goto) {
// Initialize batch state
state = {
...state,
customerId: state.customerId,
status: 'started',
startedAt: new Date().toISOString(),
results: {
checkStatus: null,
dataTransfer: null,
},
};
// Move to first step
goto('checkCustomerStatus', state);
},
checkCustomerStatus: async function (state, goto) {
try {
// Call external API to check customer status
const response = await fetch(
'https://api.example.com/customer-status',
{
method: 'POST',
body: JSON.stringify({ customerId: state.customerId }),
}
);
const data = await response.json();
// Update state with check results
state.results.checkStatus = {
status: data.status,
lastActive: data.lastActive,
checkedAt: new Date().toISOString(),
};
// Move to next step
goto('updateStatus', state);
} catch (error) {
state.error = error.message;
goto('error', state);
}
},
updateStatus: async function (state, goto) {
try {
// Update customer status in database
const newStatus =
state.results.checkStatus.lastActive < '2024-01-01'
? 'inactive'
: 'active';
await app.datastore.updateOne(
'customers',
{ customerId: state.customerId },
{ $set: { status: newStatus, updatedAt: new Date() } }
);
state.results.statusUpdated = {
newStatus,
updatedAt: new Date().toISOString(),
};
// Move to final step
goto('transferData', state);
} catch (error) {
state.error = error.message;
goto('error', state);
}
},
transferData: async function (state, goto) {
try {
// Transfer customer data to archive system
const response = await fetch('https://archive.example.com/transfer', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
customerId: state.customerId,
status: state.results.statusUpdated.newStatus,
lastActive: state.results.checkStatus.lastActive,
}),
});
state.results.dataTransfer = {
success: response.ok,
transferredAt: new Date().toISOString(),
};
// Complete the process
goto(null, state);
} catch (error) {
state.error = error.message;
goto('error', state);
}
},
error: function (state, goto) {
// Log error and end process
console.error('Batch process failed:', state.error);
state.status = 'failed';
goto(null, state);
},
}
);
// API endpoint to start batch process
app.post('/cleanup-customer', async (req, res) => {
const { customerId } = req.body;
const result = await steps.start({ customerId });
res.send(result);
});
// Helper endpoint to check batch status
app.get('/cleanup-status/:customerId', async (req, res) => {
const instances = await steps.getInstances({
customerId: req.params.customerId,
status: { $ne: 'failed' },
});
if (instances.length > 0) {
res.send(instances[0]);
} else {
res.status(404).send({ error: 'No cleanup process found' });
}
});
export default app.init();
```
This example demonstrates a straight-through batch process that:
1. **Checks Customer Status**
- Calls external API to verify customer status
- Records last active date
- Handles API errors
2. **Updates Database**
- Sets new status based on activity
- Updates customer record
- Tracks update timestamp
3. **Transfers Data**
- Sends data to archive system
- Records transfer success
- Handles transfer errors
### Sub-workflows
Sub-workflows allow you to compose complex workflows by breaking them into smaller, reusable components. A main workflow can start a sub-workflow and wait for it to complete before continuing. This pattern is useful for:
- **Modular Design**: Break complex workflows into smaller, manageable pieces
- **Reusability**: Create sub-workflows that can be used by multiple parent workflows
- **Separation of Concerns**: Keep different business logic in separate workflow definitions
- **Parallel Processing**: Run multiple sub-workflows simultaneously
- **Error Isolation**: Contain errors within sub-workflows without affecting the parent
The example below shows a main workflow that processes a number and then waits for a sub-workflow to complete before finishing. The sub-workflow updates the parent's state to signal completion.
```mermaid
graph TD
%% Main Workflow
Begin[begin] --> Decide[decide]
Decide -->|even| EvenPath[evenPath]
Decide -->|odd| OddPath[oddPath]
EvenPath --> End[end]
OddPath --> End
%% Subflow
SubBegin[subflow.begin] --> SubEnd[subflow.end]
%% Connection between workflows
End -->|wait for subflow| SubBegin
SubEnd -->|update parent state| End
%% Styling
style Begin fill:#f9f,stroke:#333,stroke-width:2px
style End fill:#ff9,stroke:#333,stroke-width:2px
style Decide fill:#9ff,stroke:#333,stroke-width:2px
style EvenPath fill:#9f9,stroke:#333,stroke-width:2px
style OddPath fill:#9f9,stroke:#333,stroke-width:2px
style SubBegin fill:#f99,stroke:#333,stroke-width:2px
style SubEnd fill:#f99,stroke:#333,stroke-width:2px
```
The diagram above shows the flow where the main workflow processes a number (even/odd branching), then **waits** for a sub-workflow to complete. The sub-workflow runs independently but communicates back to the parent through state updates. In the implementation below, you'll see how:
- The main workflow starts a sub-workflow and passes its own ID as `parentId`
- The sub-workflow completes its task and updates the parent's state
- The parent workflow continues once it detects the sub-workflow has finished
- Both workflows use the same state management system for coordination
- The main workflow uses the `wait()` function to pause execution until the sub-workflow signals completion
```javascript {54,55,68} title="subflow.js"
import { app } from 'codehooks-js';
// Create a main workflow definition
const workflow = app.createWorkflow('simpleTask', 'Basic workflow example', {
// Step 1: Initialize the workflow
begin: async function (state, goto) {
state = {
message: 'Starting workflow',
// Add a random number to demonstrate branching
value: Math.floor(Math.random() * 10),
};
goto('decide', state);
},
// Step 2: Decide which path to take
decide: async function (state, goto) {
// Branch based on whether the value is even or odd
if (state.value % 2 === 0) {
goto('evenPath', state);
} else {
goto('oddPath', state);
}
},
// Step 3a: Handle even numbers
evenPath: async function (state, goto) {
state = {
message: 'Processing even number',
path: 'even',
processed: true,
};
goto('end', state);
},
// Step 3b: Handle odd numbers
oddPath: async function (state, goto) {
state = {
message: 'Processing odd number',
path: 'odd',
processed: true,
};
goto('end', state);
},
// Step 4: End the workflow
end: async function (state, goto, wait) {
if (state.subflowCompleted) {
state = {
final_message: `Workflow completed! Processed ${state.path} number: ${state.value}`,
};
goto(null, state); // workflow complete
} else {
// Start the subflow and wait for it to complete, set the parentId in the subflow state
await subflow.start({ parentId: state._id });
wait({ wait_message: 'Waiting for subflow to complete' });
}
},
});
// Create a subflow definition
const subflow = app.createWorkflow('subflow', 'Subflow', {
begin: async (state, goto) => {
state.message = 'Hello from subflow';
goto('end', state);
},
end: async (state, goto) => {
// Update the main workflow state to indicate that the subflow has completed
workflow.updateState(
state.parentId,
{
subflowCompleted: true,
},
{ continue: true }
);
goto(null, state);
},
});
// emitted event when a workflow completes
workflow.on('completed', (data) => {
console.log('Workflow completed:', data);
});
// REST API to start a new workflow instance
app.post('/start', async (req, res) => {
const result = await workflow.start({ foo: 'bar' });
res.json(result);
});
// export app interface to serverless execution
export default app.init();
```
## Best Practices
1. **State Management**
- Keep state objects simple and serializable
- Use atomic updates for state changes
- Validate state at each step
- Monitor state size and complexity
2. **Error Handling**
- Monitor errors in logs and database
- Use try/catch in steps that might fail
- Set appropriate timeouts
- Implement retry logic for transient failures
3. **Scalability**
- Design stateless step functions
- Use appropriate queue configurations
- Monitor worker performance
- Implement proper error recovery
4. **Monitoring**
- Track workflow completion rates
- Monitor error frequencies
- Watch queue lengths
- Set up alerts for critical failures
## Related Topics
- [Queue API](/docs/queueapi) - Learn about message queues
- [Job Hooks](/docs/jobhooks) - Schedule recurring tasks
- [Data Operators](/docs/dataoperators) - Work with data in your steps
### Other popular Workflow Engines
- [Temporal](https://temporal.io/) - Open source workflow engine
- [Zeebe](https://camunda.com/platform/zeebe/) - Cloud-native workflow engine
- [AWS Step Functions](https://aws.amazon.com/step-functions/) - Serverless workflow service
- [Zapier](https://zapier.com/) - No-code workflow automation platform
- [Pipedream](https://pipedream.com/) - Developer-first workflow automation
- [n8n](https://n8n.io/) - Open source workflow automation
- [Airflow](https://airflow.apache.org/) - Platform for data pipelines
- [Prefect](https://www.prefect.io/) - Modern workflow orchestration
- [Dagster](https://dagster.io/) - Data orchestration platform
- [Make (Integromat)](https://www.make.com/) - Visual workflow automation
---
## Overview
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
import myImageUrl from './images/quickstart/collection-with-data.png';
import CodeBlock from '@theme/CodeBlock'
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
import Link from '@docusaurus/Link';
import Badge from '@mui/material/Badge';
import {Button, Grid} from '@mui/material';
import OverviewPng from './images/Overview-1.png';
import Studio1Png from './images/studio/studio1.png';
import Studio2Png from './images/studio/studio2.png';
import Studio3Png from './images/studio/studio3.png';
import Studio4Png from './images/studio/json-schema.png';
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
import FolderOpenIcon from '@mui/icons-material/FolderOpen';
import LayersIcon from '@mui/icons-material/Layers';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import {
CogIcon,
CheckCircleIcon,
CheckIcon,
DocumentCheckIcon,
RocketLaunchIcon,
CloudArrowUpIcon,
MagnifyingGlassPlusIcon,
LockClosedIcon,
PlayIcon,
CircleStackIcon,
DocumentPlusIcon,
KeyIcon,
ClockIcon,
RectangleStackIcon,
ChartBarSquareIcon,
LinkIcon,
QueueListIcon,
SparklesIcon
} from '@heroicons/react/24/outline';
export const NumberIcon = ({ index = -1, style = {}}) => (
{index}
);
export const codex0 = `import {app} from 'codehooks-js'
// Use Crudlify to create a REST API for any database collection
app.crudlify()
// bind to serverless runtime
export default app.init();
`;
export const codex1 = `const URL = 'https://myproject-fafb.api.codehooks.io/dev/orders';
response = await fetch(URL+'?status=OPEN', /* options */)
const result = await response.json()
`;
export const codex2 = `import {app, datastore} from 'codehooks-js'
// REST API route
app.get('/sales', async (req, res) => {
// highlight-next-line
const conn = await datastore.open();
const query = { status: "OPEN" };
const options = { sort: { date: 1 } }
conn.getMany('orders', query, options).json(res); // stream JSON to client
})
// bind to serverless runtime
export default app.init();
`;
export const codex3 = `import {app, datastore} from 'codehooks-js'
// REST API route
app.post('/cacheit', async (req, res) => {
const conn = await datastore.open();
const opt = {ttl: 60*1000};
// highlight-next-line
await conn.set('my_cache_key', '1 min value', opt);
res.send('Look dad, I have my own cache now!');
})
// bind to serverless runtime
export default app.init();
`;
export const codex4 = `import {app} from 'codehooks-js'
// serve the assets directory of the deployed project source code
// highlight-next-line
app.static({directory: "/assets"})
// serve a direcory of Blob storage files
// highlight-next-line
app.storage({route:"/documents", directory: "/myblobs"})
// CRUD REST API
crudlify(app)
// bind to serverless runtime
export default app.init();`;
export const codex5 = `import app from 'codehooks-js'
// Trigger a function with a CRON expression
// highlight-next-line
app.job('*/5 * * * *', (_, job) => {
console.log('Hello every 5 minutes')
// Do stuff ...
job.end()
});
export default app.init();`;
export const codex6 = `import { app, Datastore } from 'codehooks-js'\n
// register a Queue task worker
// highlight-next-line
app.worker('ticketToPDF', (req, res) => {
const { email, ticketid } = req.body.payload;
//TODO: implement code to fetch ticket data, produce PDF
// and send email with attachment using Mailgun, Sendgrid or Amazon SES
// ...and implement error handling
res.end(); // done processing queue item
})\n
// REST API
app.post('/createticket', async (req, res) => {
const { email, ticketid } = req.body;
const conn = await Datastore.open();
// highlight-next-line
await conn.enqueue("ticketToPDF", { email, ticketid });
res.status(201).json({"message": \`Check email $\{email\}\`});
})\n
export default app.init();
`;
export const codex7 = `import { app, Datastore, aggregation } from 'codehooks-js'
... // collapsed code
const spec = {
$group: {
$field: "month",
$max: ["sales", "winback"],
$sum: "sales"
}
}
const db = await datastore.open();
const dbstream = db.getMany('salesData');
// highlight-next-line
const result = await aggregation(dbstream, spec)
... // collapsed code
`;
export const codex8 = `{
"oct": {
"sales": {
"max": 1477.39,
"sum": 1234.05
},
"winback": {
"max": 22.0
}
},
"nov": {
"sales": {
"max": 2357.00,
"sum": 5432.00
},
"winback": {
"max": 91.0
}
},
}`;
export const SmallFeature = ({
icon,
link = '#',
title = 'No title',
content =
);
};
## Codehooks: Integrate and Automate everything
Codehooks provides powerful tools for developers to build, connect, and process APIs with ease. Create projects using our intuitive command line interface or web-based admin UI. A Codehooks.io project contains multiple isolated spaces - complete self-contained environments with their own database, settings, and code.
The **[`codehooks-js`](https://www.npmjs.com/package/codehooks-js)** library is the foundation for all Codehooks JavaScript development, offering a comprehensive set of 7 specialized APIs that accelerate development, streamline data integration, and simplify data processing. Explore each capability below to see how Codehooks can improve your development experience.
:::tip API Cheat Sheet
Check out the [API Cheat Sheet](/docs/apicheatsheet) for a quick overview of all the APIs
:::
If you're like most developers (i.e. smart and lazy π), we've added a [ChatGPT and LLM Prompt](/docs/chatgpt-backend-api-prompt) you can use to kickstart new projects. The prompt itself is worth looking at to get an overview of the features of the codehooks-js library. Our new MCP Server implementation has this prompt built-in.
:::tip Codehooks CLI tool
To deploy code (and much more), you use the Codehooks CLI tool:
`npm install codehooks -g`
Code editing and deployment will also soon be available in the web based UI.
:::
## API development
}
title="Get a flying start with an automatic CRUD REST API"
content={
Consider a Codehooks project, for example myproject-fafb.
Under the hood, Codehooks deploys an automatic REST API which is made up
of the following really short - but complete application code:
{codex0}
It's just 3 lines of code π₯, and it delivers a fully secure CRUD REST
API with a NoSQL database for JSON queries and updates. Client apps can
instantly connect to your data API, for example from a JavaScript app
like the example code snippet shown below:
{codex1}
}
nextsteps={[
Database REST API detailed docs,
Data validation and schema support
,
Develop custom REST APIs,
Client code examples,
Cheat Sheet for all the APIs,
]}
/>
## NoSQL and Key-Value Datastores
}
title="Easy database API inspired by MongoDB"
content={
Sometimes a standard CRUD REST API isn't enough. With custom JavaScript
functions you can create logic that integrates directly to the NoSQL
datastore. No need to think about drivers, versions, protocol or
security, the database is just there, available as an intrinsic object
in every serverless function.
{codex2}
}
nextsteps={[
Getting started,
Database API,
NoSQL query language
,
Database indexing,
Check output logs with the CLI,
Query data with the CLI,
]}
/>
}
title="Speed up your application with a Key-Value cache"
content={
The Codehooks data engine includes a simple key-value datastore. A
key-value datastore has many use-cases, and is ideal for caching due to
the swift data access and simple, maintenance-friendly architecture. A
simple example is shown below, where a string is stored for 60 seconds
with the ttl option.
{codex3}
}
nextsteps={[
Key-Value API,
Tutorials,
]}
/>
## Background Jobs
}
title="CRON expressions + your function keeps running"
content={
Use simple CRON expressions to automate and manage the execution of
routine tasks. You can schedule a job to run at multiple, disparate
times. For example, you can schedule a task to run at 2 AM on Sundays
and 4 PM on Wednesdays with the same CRON job.
In the simple example below, the CRON expression{' '}
*/5 * * * * will trigger a JavaScript function each 5
minutes.
}
title="Scale workloads with queues and worker functions"
content={
Use persistent queues and "worker" functions to create robust,
efficient, and highly responsive back-end systems. Queues scales
processing across multiple nodes and will restore and continue working
in case of errors and system failures.
Check out the example below, where a REST API request starts a complex
task by adding it to the Queue for later processing.
Create and deploy robust, scalable workflows using queued functions and state management.
Build reliable backend systems with automatic retry, state persistence, and distributed processing.
The example below shows a simple workflow that processes customer onboarding with email validation.
{`import { app } from 'codehooks-js';
const workflow = app.createWorkflow('customerOnboarding', 'Customer signup and email validation', {
START: async function (state, goto) {
state = { email: state.email, validated: false };
await sendWelcomeEmail(state.email);
goto('waitForValidation', state);
},
waitForValidation: async function (state, goto) {
if (state.validated) {
goto('complete', state);
} else {
wait({wait_message: 'Waiting for subflow to complete'});
}
},
complete: function (state, goto) {
state.status = 'completed';
goto(null, state); // workflow complete
}
});
// Start workflow from REST API
app.post('/start-onboarding', async (req, res) => {
const result = await workflow.start({ email: req.body.email });
res.send(result);
});`}
}
nextsteps={[
Workflow API,
Example Workflows
]}
/>
## File System & Blob storage
}
title="Serve any document or file content"
content={
Codehooks has a built-in file system to support streaming file uploads
and downloads. You can serve files via a static fileserver or via
JavaScript functions using the File API. The example CLI command below
shows how to upload a directory of files to the Blob storage{' '}
dev space (environment) of the project. You can publish
documents and files in two ways. Use the file-upload CLI command for
mass uploads of files/directories:
Or deploy file content located as part of your server code with the
deploy command:
$ coho deploy
The example below shows how to serve both deployed files and Blob
storage content.
{codex4}
}
nextsteps={[
File API,
Upload files with the CLI,
]}
/>
## Data Aggregation
}
title="Easy to use data aggregation format"
content={
Transform your live data streams into insights with just a few lines of
code. Codehooks has a simple but powerful JSON-based aggregation
expression language to specify data transformations. Groups and sums can
be nested to create both simple and advanced data outputs. The
aggregation API can process input from any JSON data-stream, for example
the query output from the NoSQL datastore. π₯
{codex7}
The example aggreagation transformation produces JSON output similar to
the example below.
{codex8}
}
nextsteps={[Aggregation API]}
/>
## Next Steps
Read this far? Great π
It's time to get your feet wet π¦. Why don't you start thinking about how codehooks' toolbox can help you build your solution. You can get started with the [quickstart for developers](/docs/quickstart-cli) or just [sign up](https://account.codehooks.io) and create your project and space using the web UI. You can easily upload some CSV data and immediately test your new API.
The documentation or tutorials are great places to start. If you need more help, feel free to contact us on the chat.
---
## Data aggregation API
*(Note: This content contains MDX/JSX code)*
The JSON aggregation specification is designed to provide a structured way to aggregate JSON data streams. It offers several operators to aid in the summarization and grouping of data streams without the need for extensive code.
**Aggregation Operators**
Our specification currently supports the following operators:
- `$max`: Determines the maximum value for the specified field(s).
- `$min`: Determines the minimum value for the specified field(s).
- `$avg`: Computes the average value for the specified field(s).
- `$sum`: Sums up the values for the specified field(s).
- `$group`: Group by specified field and perform nested aggregations.
Each operator can take either a string denoting the field name or an object with additional properties such as $field (for specifying the field name) and $label (for specifying a custom label for the result).
## Aggregation data from a NoSQL query
JSON data `aggregation` is a standard library that is imported in your Codehooks app.
The example below shows how to use the library agains a `salesData` NoSQL data collection.
```js {5-8,12}
import { app, datastore, aggregation } from 'codehooks-js';
app.post('/agg', async (req, res) => {
try {
const spec = {
$max: 'sales',
$avg: 'sales',
};
const db = await datastore.open();
const dbstream = db.getMany('salesData', { active: true });
const result = await aggregation(dbstream, spec);
console.log(result);
res.json(result);
} catch (error) {
console.log(error.message);
res.status(400).end(error);
}
});
export default app.init(); // Bind functions to the serverless runtime
```
**Example output**
```json
{
"count": 120500,
"sales": {
"max": 1234.0,
"avg": 123.0,
"sum_total": 123456.0
}
}
```
## Operators
### `$max`
- **Description:** Determines the maximum value for the specified field(s).
- **Usage:**
- `$max: fieldName` for a simple field.
- `$max: { $field: fieldName, $label: customLabel }` for specifying a custom label.
- **Example:**
```json
{ "$max": { "$field": "sales", "$label": "Max Sales" } }
```
### `$min`
- **Description:** Determines the minimum value for the specified field(s).
- **Usage:**
- `$min: fieldName` for a simple field.
- `$min: { $field: fieldName, $label: customLabel }` for specifying a custom label.
- **Example:**
```json
{ "$min": { "$field": "sales", "$label": "Min Sales" } }
```
### `$avg`
- **Description:** Computes the average value for the specified field(s).
- **Usage:**
- `$avg: fieldName` for a simple field.
- `$avg: { $field: fieldName, $label: customLabel }` for specifying a custom label.
- **Example:**
```json
{ "$avg": { "$field": "sales", "$label": "Average Sales" } }
```
### `$sum`
- **Description:** Sums up the values for the specified field(s).
- **Usage:**
- `$sum: fieldName` for a simple field.
- `$sum: { $field: fieldName, $label: customLabel }` for specifying a custom label.
- **Example:**
```json
{ "$sum": { "$field": "sales", "$label": "Total Sales" } }
```
### `$group`
- **Description:** Group by specified field and perform nested aggregations.
- **Usage:**
- `$group: { $field: fieldName, ...nestedAggregations }` to group by a field and apply further aggregations.
- **Example:**
```json
{
"$group": {
"$field": "customerType",
"$label": "Customer Segment",
"$max": "sales"
}
}
```
#### Example: Complex grouping and aggregation
```js
const spec = {
$max: { $field: 'sales', $label: 'All time top sales' },
$group: [
{
$field: 'customerType',
$label: 'Segment',
$max: 'sales',
$min: { $field: 'sales', $label: 'Low' },
$group: {
$field: 'year',
$group: {
$field: 'month',
$max: ['sales', { $field: 'volume', $label: 'Sales volume' }],
},
},
},
{
$field: 'day',
$max: 'sales',
},
],
};
```
Our JSON aggregation specification provides a robust tool for developers seeking powerful and streamlined data processing capabilities. Harnessing these operators allows for comprehensive insights into your data streams.
---
## Supabase Pricing vs Codehooks: Complete Comparison Guide 2025
# Supabase Pricing vs Codehooks: Complete Comparison Guide 2025
## Understanding Supabase Pricing
Supabase pricing follows a tiered approach designed to accommodate projects of all sizes:
- **Free Tier**: Perfect for hobby projects and learning, offering unlimited API requests, 500MB database, and 1GB storage at no cost.
- **Pro Tier ($25/month)**: Ideal for growing projects, providing 8GB database, 100GB storage, and daily backups.
- **Team Tier ($599/month)**: Built for professional teams, featuring custom limits, advanced security, and priority support.
Supabase charges based on:
- Database size and usage
- Storage consumption
- Bandwidth usage
- Additional features like compute credits
The platform offers a generous free tier that's perfect for getting started, with clear upgrade paths as your project grows. Their pricing model is particularly attractive for projects that need unlimited API requests, as this is included in all tiers.

## Different Solutions for Different Needs
While Supabase and Codehooks are both powerful backend platforms, they serve fundamentally different purposes and excel in different scenarios. Think of them as different tools in your development toolkit - each optimized for specific use cases.
### Supabase: The Full-Stack Backend Platform
Supabase is ideal when you need:
- Traditional SQL database with complex relationships
- Enterprise-grade security features
- Advanced authentication with multiple providers
- Complex data modeling with strict schemas
- Full-stack application development
- Large-scale deployments with custom requirements
### Codehooks: The Rapid Integration & Automation Platform
Codehooks shines when you need:
- Quick API development and deployment
- Simple data ingestion and transformation
- Webhook endpoints and integrations
- Scheduled tasks and automation workflows
- Real-time data collection and processing
- Rapid prototyping and MVP development
While there is some overlap in capabilities, choosing between them often depends on your primary use case rather than just comparing features side by side. The following comparison will help you understand their differences in detail, but remember that the "better" choice depends entirely on your specific needs.
## Quick Pricing Overview
| Feature | Supabase Free | Codehooks Free | Supabase Pro ($25) | Codehooks Pro ($19) | Supabase Team ($599) | Codehooks Premium ($79) |
| ------------ | ------------- | -------------- | ------------------ | ------------------- | -------------------- | ----------------------- |
| Developers | Unlimited | 2 | Unlimited | 3 | Unlimited | 10 |
| API Calls | Unlimited | 60/min | Unlimited | 600/min | Unlimited | Full-speed |
| Database | 500MB | 150MB\* | 8GB | 10GB\* | Custom | 50GB\* |
| Storage | 1GB | 1GB | 100GB | 100GB | Custom | 250GB |
| Support | Community | Community | Email | Best Effort | Priority | Prioritized |
| Backups | No | No | Daily | Daily | Daily | Daily |
| Environments | 1 Project | 1 Space | Multiple Projects | 1 Space | Multiple Projects | 3 Spaces |
\* NoSQL databases typically use significantly less storage space than SQL databases due to their schema-less nature and efficient storage patterns. The listed storage limits for Codehooks may effectively store more data than equivalent SQL storage limits. 1GB of JSON data with 1KB per document is approximately 1 million documents.
## Ideal Use Cases: Where Each Platform Shines
### Codehooks: The Rapid Integration & Automation Platform
#### Perfect for Integration Projects
- **Webhook Endpoints**: Create webhook receivers in seconds
- **Data Ingestion APIs**: Quick setup for data collection
- **Third-party Integrations**: Connect with external services
- **Data Transformation**: Simple ETL pipelines
- **Real-time Data Collection**: Built-in SSE for live updates
#### Ideal for Automation
- **Scheduled Tasks**: Built-in job scheduling
- **Worker Queues**: Background job processing
- **Event-driven Workflows**: Automated responses
- **Data Synchronization**: Multi-source data sync
- **IoT Data Processing**: Real-time sensor data handling
#### Great for Rapid Development
- **Prototyping**: Quick API development
- **MVPs**: Fast feature testing
- **Internal Tools**: Admin dashboards
- **Microservices**: Small, focused APIs
- **Serverless Functions**: Event handlers
### Supabase: The Full-Stack Backend Platform
#### Perfect for Traditional Applications
- **Web Applications**: Full-stack development
- **Mobile Apps**: Complete backend solution
- **Enterprise Systems**: Advanced security features
- **Complex Data Models**: SQL relationships
- **Large-scale Applications**: Enterprise-grade features
## Development Experience Comparison
### Codehooks: Speed and Simplicity
- **Instant Deployment**: < 1 second deployment time
- **Online IDE**: Built-in coding environment
- **Data Manager**: Visual data management
- **CLI Tools**: Powerful automation
- **Simple Authentication**: Quick auth setup
- **NoSQL Flexibility**: Easy data modeling
- **Real-time Updates**: SSE implementation
- **Built-in Tools**: Everything included
- **Space Management**: Isolated environments
- **Environment Variables**: Per-space configuration
### Supabase: Power and Flexibility
- **SQL Database**: Advanced querying
- **Row Level Security**: Fine-grained access
- **Database Extensions**: Rich ecosystem
- **Advanced Auth**: Multiple providers
- **WebSocket Real-time**: Bi-directional
- **Open Source**: Full customization
- **Enterprise Features**: Advanced security
## When to Choose Each Platform
### Choose Codehooks When You Need:
- **Quick Integration Development**
- Fast API creation
- Simple data ingestion
- Webhook endpoints
- Data transformation
- **Automation Projects**
- Scheduled tasks
- Background jobs
- Event processing
- Data synchronization
- **Rapid Prototyping**
- Quick MVPs
- Feature testing
- Internal tools
- Microservices
### Choose Supabase When You Need:
- **Complex Data Relationships**
- SQL databases
- Advanced queries
- Data integrity
- Complex schemas
- **Enterprise Features**
- Advanced security
- Compliance
- Custom extensions
- Large-scale deployments
- **Full-Stack Applications**
- Complete backend
- Complex auth
- Advanced real-time
- Custom solutions
## Detailed Pricing Breakdown
### Supabase Pricing Tiers
#### Free Tier - Perfect for Hobby Projects
- **Cost**: $0/month
- **Best for**: Students, hobbyists, and small projects
- **Key Features**:
- Unlimited API requests
- 50,000 monthly active users
- 500MB database space
- 5GB bandwidth
- 1GB file storage
- Community support
#### Pro Tier - For Growing Projects
- **Cost**: $25/month
- **Best for**: Small to medium businesses
- **Key Features**:
- 100,000 monthly active users
- 8GB database space
- 250GB bandwidth
- 100GB file storage
- Email support
- Daily backups
- 7-day log retention
- $10 in compute credits
#### Team Tier - For Professional Teams
- **Cost**: $599/month
- **Best for**: Professional teams and businesses
- **Key Features**:
- SOC2 compliance
- HIPAA compliance (add-on)
- Read-only and billing member roles
- SSO for dashboard
- Priority email support and SLAs
- 14-day backup retention
- 28-day log retention
### Codehooks Pricing Tiers
#### Development (Free) - For Small Projects
- **Cost**: $0/month
- **Best for**: Small projects and prototypes
- **Key Features**:
- 2 Developers
- 60 API and Function calls/minute
- 150MB Database Storage
- 1GB File Storage
- 1 Custom Domain with SSL
- 1 Space
#### Pro - For Growing Teams
- **Cost**: $19/month
- **Best for**: Small teams and startups
- **Key Features**:
- 3 Developers
- 600 API and Function calls/minute
- 10GB Database Storage
- 100GB File Storage
- 2 Custom Domains
- 1 Space
- Daily Backups
- Best Effort Support
- Extra resources available:
- Developers: $5 each
- Database Storage: $0.1/GB
- File Storage: $0.02/GB
- Domains: $10 each
- Spaces: $10 each
#### Premium - For Professional Teams
- **Cost**: $79/month
- **Best for**: Professional teams
- **Key Features**:
- 10 Developers
- Full-speed API and Functions
- 50GB Database Storage
- 250GB File Storage
- 5 Custom Domains
- 3 Spaces
- Daily Backups
- Prioritized Support
- Extra resources available:
- Developers: $5 each
- Database Storage: $0.1/GB
- File Storage: $0.02/GB
- Domains: $10 each
- Spaces: $10 each
## Cost Comparison: Which is More Affordable?
### For Small Projects
- **Supabase Free**: Unlimited API calls, 500MB database, 1GB storage
- **Codehooks Free**: 60 API calls/min, 150MB database, 1GB storage
- **Winner**: Supabase offers more generous API and database limits, with equal storage
### For Growing Projects
- **Supabase Pro**: $25/month, 8GB database, 100GB storage
- **Codehooks Pro**: $19/month, 10GB database, 100GB storage
- **Winner**: Codehooks offers better value with more database storage at a lower price point
### For Professional Teams
- **Supabase Team**: $599/month, custom limits
- **Codehooks Premium**: $79/month, 50GB database, 250GB storage
- **Winner**: Codehooks offers significantly lower pricing with generous storage limits
## Additional Costs to Consider
### Supabase Additional Costs
- Extra storage: Pay for additional storage beyond included limits
- Compute credits: Additional compute resources beyond included credits
- HIPAA compliance: Additional cost for healthcare projects
- Enterprise features: Custom pricing for advanced features
### Codehooks Additional Costs
- Developers: $5 each
- Database Storage: $0.1/GB
- File Storage: $0.02/GB
- Domains: $10 each
- Spaces: $10 each
## Frequently Asked Questions About Pricing
### Which platform is more cost-effective for startups?
Codehooks generally offers more affordable pricing for startups, especially in the Pro tier ($19 vs $25). However, Supabase's free tier provides more generous limits for initial development.
### How do the platforms handle scaling costs?
- **Supabase**: Costs increase with database size, bandwidth, and compute usage
- **Codehooks**: Predictable pricing with included compute costs, pay-as-you-grow for storage
### Which platform offers better value for enterprise projects?
This question is difficult to answer as it depends on the use case.
- **Supabase**: Better for enterprises needing advanced compliance and security features
- **Codehooks**: Better for enterprises needing predictable pricing and simpler scaling
## Feature Comparison
### Codehooks Strengths
- **Lightweight & Quick Setup**: Instant deployment (< 1 second)
- **Simplified Development**: Less code needed for common operations
- **Unified NoSQL Database**: Easy access to both document and key-value databases
- **Built-in Tools**:
- Online coding environment
- Data manager interface
- Powerful CLI for automations
- **Serverless Functions**: Native JS/TS support
- **Job Scheduling & Worker Queues**: Built-in task management
- **Automatic CRUD**: Simplified data operations
- **Simple Authentication**: codehooks-auth integration
- **Real-time Capabilities**: Via Server-Sent Events (SSE)
- **Compute Costs Included**: No separate compute billing
### Supabase Strengths
- **Open Source**: Full access to source code
- **PostgreSQL Database**: Enterprise-grade SQL database
- **Advanced Security**: Row Level Security (RLS)
- **Real-time Subscriptions**: WebSocket-based
- **Extensive Documentation**: Large community resources
- **Enterprise Features**: Advanced compliance options
- **Database Extensions**: Rich ecosystem of PostgreSQL extensions
## Use Cases
### Codehooks is Ideal For:
- Microservices
- Rapid prototyping and MVP development
- Projects requiring quick deployment
- Teams wanting minimal setup and configuration
- Applications needing simple NoSQL databases
- Projects with straightforward authentication needs
- Teams preferring a lightweight, all-in-one solution
### Supabase is Ideal For:
- SaaS applications
- Enterprise applications requiring SQL
- Projects needing advanced security features
- Teams requiring extensive customization
- Applications with complex data relationships
- Projects needing advanced compliance features
- Teams preferring open-source solutions
## Key Differentiators
### Codehooks
1. **Speed of Development**: Faster setup and deployment
2. **Simplified Architecture**: Less code, more functionality
3. **Unified NoSQL Approach**: Easier data modeling
4. **All-in-One Solution**: Integrated tools and features
5. **Predictable Pricing**: Compute costs included
### Supabase
1. **Open Source**: Full control and customization
2. **SQL Power**: Advanced database capabilities
3. **Enterprise Features**: Advanced security and compliance
4. **Community**: Larger ecosystem and resources
5. **Database Extensions**: Rich PostgreSQL ecosystem
## Migration Considerations
### Migrating to Codehooks
- Simple migration path for NoSQL-based applications
- Quick setup process
- Minimal configuration required
- Built-in tools for data management
### Migrating to Supabase
- More complex migration for SQL-based applications
- Requires database schema planning
- More configuration options
- Extensive documentation available
## Codehooks Spaces: The Power of Separate Environments
Codehooks has a unique feature called Spaces. Spaces are totally self-contained environments with their own storage, database, code, settings and domains. It's well suited to use for having development, staging and production environments in the same project, but can also be used for different backend / API functionality in the same project. While Supabase uses separate projects for different environments, Codehooks Spaces allow you to manage multiple environments within a single project, making it easier to maintain and deploy across different stages of development.
### Space Management Across Tiers
- **Free Tier**: 1 Space for small projects
- **Pro Tier**: 1 Space for growing projects
- **Premium Tier**: 3 Spaces for professional teams
- **Additional Spaces**: $10/month each
## Conclusion
While both platforms offer robust solutions, they serve different needs:
- **Codehooks** excels in rapid development, integration, and automation scenarios, making it ideal for:
- Quick API development
- Data collection systems
- Integration projects
- Automation workflows
- Prototyping and MVPs
- **Supabase** shines in traditional application development, making it suitable for:
- Full-stack applications
- Complex data models
- Enterprise requirements
- Large-scale deployments
Choose Codehooks if you value speed, simplicity, and rapid development for integration and automation projects. Choose Supabase if you need enterprise-grade features, complex data relationships, or advanced security features.
---
## Codehooks.io API Cheat Sheet
*(Note: This content contains MDX/JSX code)*
**Everything you need to build a serverless backend in one place.** This cheat sheet contains all essential APIs for creating complete serverless applications with Codehooks.io. Quick reference for routing, authentication, NoSQL databases, workflows, queues, key-value stores, and real-time features. Each API includes direct links to detailed documentation with examples and usage patterns.
## **HTTP Routing APIs**
- **`post(path, function)`** - Register POST route handlers β [Details](/docs/appeventapi#apppostroute-workerfunction)
- **`get(path, function)`** - Register GET route handlers β [Details](/docs/appeventapi#appgetroute-workerfunction)
- **`put(path, function)`** - Register PUT route handlers β [Details](/docs/appeventapi#appputroute-workerfunction)
- **`patch(path, function)`** - Register PATCH route handlers β [Details](/docs/appeventapi#apppatchroute-workerfunction)
- **`delete(path, function)`** - Register DELETE route handlers β [Details](/docs/appeventapi#appdeleteroute-workerfunction)
- **`all(path, function)`** - Register handlers for all HTTP methods β [Details](/docs/appeventapi#appallroute-workerfunction)
## **Middleware & Authentication APIs**
- **`use(function)`** - Register global middleware β [Details](/docs/appeventapi#appuseworkerfunction)
- **`auth(path, function)`** - Register authentication middleware β [Details](/docs/appeventapi#appauthroute-workerfunction)
- **`static(options, function?)`** - Serve static files from source code β [Details](/docs/appeventapi#appstaticoptions-callback)
- **`storage(options, function?)`** - Serve files from blob storage β [Details](/docs/appeventapi#appstorageoptions-callback)
## **Database/NoSQL APIs**
### **Connection & Setup**
- **`Datastore.open()`** - Connect to datastore and return API interface β [Details](/docs/nosql-database-api#datastoreopen)
### **Document Operations**
- **`insertOne(collection, document)`** - Insert new document β [Details](/docs/nosql-database-api#insertonecollection-document)
- **`getOne(collection, query)`** - Get single document by ID/query β [Details](/docs/nosql-database-api#getonecollection-id--query)
- **`findOne(collection, query)`** - Alias for getOne β [Details](/docs/nosql-database-api#findonecollection-id--query)
- **`getMany(collection, query?, options?)`** - Get stream of documents β [Details](/docs/nosql-database-api#getmanycollection-query-options)
- **`find(collection, query?, options?)`** - Alias for getMany β [Details](/docs/nosql-database-api#findcollection-query-options)
- **`toArray(collection, query?, options?)`** - Get documents as array β [Details](/docs/nosql-database-api#toarraycollection-query-options)
- **`updateOne(collection, query, document, operators?, options?)`** - Update document (patch) β [Details](/docs/nosql-database-api#updateonecollection-id--query-updateoperators-options)
- **`updateMany(collection, query, document, operators?, options?)`** - Update multiple documents β [Details](/docs/nosql-database-api#updatemanycollection-query-document-options)
- **`replaceOne(collection, query, document, options?)`** - Replace document completely β [Details](/docs/nosql-database-api#replaceonecollection-id--query-document-options)
- **`replaceMany(collection, query, document, options?)`** - Replace multiple documents β [Details](/docs/nosql-database-api#replacemanycollection-query-document-options)
- **`removeOne(collection, query)`** - Remove document β [Details](/docs/nosql-database-api#removeonecollection-id--query)
- **`removeMany(collection, query, options?)`** - Remove multiple documents β [Details](/docs/nosql-database-api#removemanycollection-query-options)
### **Schema Management**
- **`setSchema(collection, schema)`** - Add JSON-Schema validation β [Details](/docs/nosql-database-api#setschemacollection-schema)
- **`getSchema(collection)`** - Get collection schema β [Details](/docs/nosql-database-api#getschemacollection)
- **`removeSchema(collection)`** - Remove JSON-Schema validation β [Details](/docs/nosql-database-api#removeschemacollection)
### **Collection Management**
- **`createCollection(collection, options?)`** - Create new collection β [Details](/docs/nosql-database-api#createcollectioncollection-options)
- **`dropCollection(collection)`** - Delete collection β [Details](/docs/nosql-database-api#dropcollectioncollection)
## **Key-Value Store APIs**
- **`set(key, value, options?)`** - Set key-string value pair with optional TTL β [Details](/docs/key-value-database-api#setkey-value-options)
- **`setObj(key, value, options?)`** - Set key-object pair with optional TTL β [Details](/docs/key-value-database-api#setobjkey-value-options)
- **`get(key, options?)`** - Get string value by key β [Details](/docs/key-value-database-api#getkey-options)
- **`getObj(key, options?)`** - Get object value by key β [Details](/docs/key-value-database-api#getobjkey-options)
- **`getAll(keyPattern, options?)`** - Get all key-value pairs matching pattern β [Details](/docs/key-value-database-api#getallkeypattern-options)
- **`del(key, options?)`** - Delete key-value pair β [Details](/docs/key-value-database-api#delkey-options)
- **`delAll(key, options?)`** - Delete all key-value pairs matching pattern β [Details](/docs/key-value-database-api#delallkey-options)
- **`incr(key, value, options?)`** - Increment numeric value β [Details](/docs/key-value-database-api#incrkey-number-options)
- **`decr(key, value, options?)`** - Decrement numeric value β [Details](/docs/key-value-database-api#decrkey-number-options)
## **Queue & Background Processing APIs**
- **`queue(topic, function)`** - Register queue handlers β [Details](/docs/queuehooks)
- **`worker(name, function)`** - Register worker functions β [Details](/docs/appeventapi#appworkername-workerfunction)
- **`enqueue(topic, document, options?)`** - Add job to queue β [Details](/docs/queueapi#enqueuetopic-payload-options)
- **`enqueueFromQuery(collection, query, topic, options?)`** - Queue items from database query β [Details](/docs/queueapi#enqueuefromquerycollection-query-topic-options)
## **Job Scheduling APIs**
- **`job(cronExpression, function)`** - Register scheduled cron jobs β [Details](/docs/jobhooks#cron-jobs)
- **`schedule.runAt(when, data, worker)`** - Schedule one-time delayed job β [Details](/docs/jobhooks#schedule-delayed-worker-function-dynamically-with-runat)
- **`schedule.run(data, worker)`** - Execute worker immediately β [Details](/docs/jobhooks#schedule-worker-function-to-run-immediate)
## **Workflow APIs**
### **Workflow Management**
- **`createWorkflow(name, description, steps)`** - Create new workflow definition β [Details](/docs/workflow-api#createworkflowname-description-steps)
- **`start(initialState)`** - Start new workflow instance β [Details](/docs/workflow-api#startinitialstate)
- **`updateState(instanceId, state, options?)`** - Update workflow state β [Details](/docs/workflow-api#updatestateinstanceid-state-options)
- **`setState(instanceId, stateData)`** - Set complete workflow state β [Details](/docs/workflow-api#setstateinstanceid-statedata)
- **`continue(instanceId, reset?)`** - Continue paused workflow β [Details](/docs/workflow-api#continueinstanceid-reset)
- **`getWorkflowStatus(id)`** - Get workflow status β [Details](/docs/workflow-api#getworkflowstatusid)
- **`getInstances(filter)`** - List workflow instances β [Details](/docs/workflow-api#getinstancesfilter)
- **`cancelWorkflow(id)`** - Cancel workflow instance β [Details](/docs/workflow-api#cancelworkflowid)
### **Error Recovery & Monitoring**
- **`continueAllTimedOut()`** - Continue all timed out workflows β [Details](/docs/workflow-api#continuealltimedout)
- **`findTimedOutSteps(filter?)`** - Find timed out workflow steps β [Details](/docs/workflow-api#findtimedoutstepsfilter)
### **Event Management**
- **`on(event, listener)`** - Register event listener β [Details](/docs/workflow-api#onevent-listener)
- **`once(event, listener)`** - Register one-time event listener β [Details](/docs/workflow-api#onceevent-listener)
- **`off(event, listener)`** - Remove event listener β [Details](/docs/workflow-api#offevent-listener)
- **`emit(event, data)`** - Emit custom event β [Details](/docs/workflow-api#emitevent-data)
### **Configuration**
- **`configure(options)`** - Configure workflow settings β [Details](/docs/workflow-api#configureoptions)
## **File Management APIs**
- **`filestore.readFile(path)`** - Read file content as text β [Details](/docs/fileapi#readfilepath)
- **`filestore.readFileAsBuffer(path)`** - Read file content as buffer β [Details](/docs/fileapi#readfileasbufferpath)
- **`filestore.getReadStream(path)`** - Read file as binary stream β [Details](/docs/fileapi#getreadstreampath)
- **`filestore.saveFile(path, filestream)`** - Write binary stream to file β [Details](/docs/fileapi#savefilepath-filestream)
- **`filestore.deleteFile(path)`** - Delete file β [Details](/docs/fileapi#deletefilepath)
- **`filestore.list(path)`** - List files in directory β [Details](/docs/fileapi#listpath-options)
## **Real-time Communication APIs**
- **`realtime.createChannel(channel)`** - Create real-time channel β [Details](/docs/realtimeapi#realtimecreatechannelchannel)
- **`realtime.publishEvent(channel, data, query?)`** - Publish event to channel β [Details](/docs/realtimeapi#realtimepublisheventchannel-data-query)
- **`realtime.createListener(channel, data)`** - Create listener for channel β [Details](/docs/realtimeapi#realtimecreatelistenerchannel-data)
- **`realtime.getListener(channel, listenerID)`** - Get specific listener β [Details](/docs/realtimeapi#realtimegetlistenerchannel-listenerid)
- **`realtime.getListeners(channel)`** - Get all listeners for channel β [Details](/docs/realtimeapi#realtimegetlistenerschannel)
- **`realtime.removeListener(channel, listenerID)`** - Remove listener β [Details](/docs/realtimeapi#realtimeremovelistenerchannel-listenerid)
## **Template & Configuration APIs**
- **`set(key, val)`** - Set application configuration β [Details](/docs/appeventapi)
- **`crudlify(schema?, options?)`** - Auto-generate CRUD REST API β [Details](/docs/appeventapi#appcrudlifyschema-options)
## **Application Lifecycle APIs**
- **`init(function?)`** - Initialize application and return manifest β [Details](/docs/appeventapi#appinit)
- **`start(function?)`** - Alias for init() method β [Details](/docs/appeventapi#appinit)
## **Database REST API Endpoints**
When using `crudlify()`, these endpoints are automatically created:
| Operation | Method | Endpoint | Description |
| --------- | -------- | -------------------- | ---------------------------------------------------------------------------------- |
| List all | `GET` | `/:collection` | Get all documents β [Details](/docs/database-rest-api#get-list-of-documents) |
| Query | `GET` | `/:collection?query` | Get documents by query β [Details](/docs/database-rest-api#get-documents-by-query) |
| Get by ID | `GET` | `/:collection/:id` | Get single document β [Details](/docs/database-rest-api#get-document-by-id) |
| Create | `POST` | `/:collection` | Create new document β [Details](/docs/database-rest-api#create-a-new-document) |
| Update | `PATCH` | `/:collection/:id` | Update document β [Details](/docs/database-rest-api#update-a-document-by-id) |
| Replace | `PUT` | `/:collection/:id` | Replace document β [Details](/docs/database-rest-api#replace-a-document-by-id) |
| Delete | `DELETE` | `/:collection/:id` | Delete document β [Details](/docs/database-rest-api#delete-a-document-by-id) |
## **Authentication Methods**
- **API Tokens** - Use `x-apikey` header for app-to-app authentication β [Details](/docs/authentication#app-to-app-authentication-with-api-tokens)
- **JWT/JWKS** - Configure JWT authentication via Auth0, Clerk, etc β [Details](/docs/authentication#authenticate-users-with-jwt-using-jwks)
- **Custom Auth** - Use `codehooks-auth` package for complete control β [Details](/docs/authentication#custom-authentication-with-codehooks-auth)
## **Query Syntax Examples**
### NoSQL Queries
```javascript
// Simple equality
{
status: 'active';
}
// Comparison operators
{
age: {
$gt: 18;
}
}
{
price: {
$lte: 100;
}
}
// Logical operators
{
$and: [{ status: 'active' }, { age: { $gte: 18 } }];
}
// Array operations
{
tags: {
$in: ['javascript', 'nodejs'];
}
}
// Regular expressions
{
name: {
$regex: /john/i;
}
}
```
### URL Query Parameters
```bash
# Simple query
?status=active&limit=10
# Advanced query
?q={"age":{"$gt":18}}&sort=name&fields=name,email
```
## **Common Usage Patterns**
### Basic API Setup
```javascript
import { app } from 'codehooks-js';
// Auto-generate CRUD API
app.crudlify();
// Custom routes
app.get('/hello', (req, res) => {
res.json({ message: 'Hello World' });
});
export default app.init();
```
### Database Operations
```javascript
import { Datastore } from 'codehooks-js';
async function myFunc() {
const conn = await Datastore.open();
// Insert document
const doc = await conn.insertOne('users', { name: 'John' });
// Query documents
const users = await conn.getMany('users', { active: true }).toArray();
// Key-value operations
await conn.set('session:123', { userId: 'abc' }, { ttl: 3600000 });
}
...
```
### Background Processing
```javascript
// Register worker
app.worker('emailWorker', async (req, res) => {
console.log('Processing email:', req.body.payload);
res.end();
});
// Queue job
async function myFunc() {
const conn = await Datastore.open();
await conn.enqueue('emailWorker', { email: 'user@example.com' });
}
...
```
### Workflow Processing
```javascript
// Create a workflow with persistent state
const workflow = app.createWorkflow(
'orderProcessing',
'Process customer orders',
{
start: async (state, goto) => {
state.orderId = state.orderId || generateOrderId();
goto('validatePayment', state);
},
validatePayment: async (state, goto) => {
// Validate payment logic here
if (state.paymentValid) {
goto('fulfillOrder', state);
} else {
goto('handlePaymentError', state);
}
},
fulfillOrder: async (state, goto) => {
state.status = 'fulfilled';
goto(null, state); // Complete workflow
},
}
);
// Start workflow instance
const orderWorkflow = await workflow.start({
customerId: '123',
amount: 99.99,
});
```
---
**Links:**
- [Complete Documentation](/docs)
- [Getting Started Guide](/docs/quickstart-cli)
- [Code Examples](/docs/examples/examples-overview)
- [CLI Reference](/docs/cli)
- [Prompt for development using ChatGPT, Claude, Cursor etc](/docs/chatgpt-backend-api-prompt)
- [MCP Server for Agent based development](https://github.com/RestDB/codehooks-mcp-server)
---
## The Application Object
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
The Application object creates events that lets you hook your serverless functions accordingly.
```mermaid
flowchart LR
event("REST API events") & job("Background events") --> hook("App hook") --> func("Function") -.- db[(Database)]
```
Available event types are:
- REST API route events
- Background Job cron and scheduled events
- Worker Queue events
- Authentication events
The event hooks is an Node.js Express style JavaScript library in a [public npm library](https://www.npmjs.com/package/codehooks-js) that you install as a npm package in your project directory. You can check for updates in the npm version.
Install the CLI.
```bash
npm i codehooks-js
```
Deploy your app with the CLI command.
```bash
coho deploy
```
**API quick overview**
## Complete code example
Import the `app` library in your application and hook into the various events.
```js
import { app } from 'codehooks-js';
// generic serverless function
function fooFunc(req, res) {
res.end();
}
// generic middleware function
function fooMiddleware(req, res, next) {
next();
}
/*
* Hook into events
*/
app.use(fooMiddleware); // global middleware
app.use('/foo', fooMiddleware); // global middleware on routes
app.get('/foo', fooFunc); // GET
app.post('/foo', fooFunc); // POST
app.put('/foo', fooFunc); // PUT
app.patch('/foo', fooFunc); // PATCH
app.delete('/foo', fooFunc); // DELETE
app.all('/foo', fooFunc); // GET, POST, PUT, PATCH, DELETE
app.auth('/*', fooMiddleware); // Called before outher routes without access tokens
app.job('* * * * *', fooFunc); // subscribe to cron events
app.worker('workername', fooFunc); // subscribe to worker events
app.static(options); // serve deployed static files and content
app.storage(options); // serve dynamic uploaded files and content
app.crudlify(options); // Database CRUD REST API
// Bind events and functions to the serverless runtime
export default app.init();
```
## app.init()
Mandatory call to bind your functions to events from the serverless runtime.
**Parameters** none
## app.use(workerFunction)
Adds global middleware to all route API events (get, put, post, patch, delete).
**Parameters**
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object), [next](rest-api-app-routes#middleware-functions))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
import cookieParser from 'cookie-parser';
// external npm lib middleware
app.use(cookieParser());
app.get('/foo', function (req, res) {
// Cookies that have not been signed
console.log('Cookies: ', req.cookies);
// Cookies that have been signed
console.log('Signed Cookies: ', req.signedCookies);
// Custom middleware result
console.log('Foo: ', req.foo);
res.send('Done');
});
export default app.init();
```
## app.use(route, workerFunction)
Adds global middleware to a specific route API events (get, put, post, patch, delete).
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object), [next](rest-api-app-routes#middleware-functions))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
// custom
app.use('/foo', (req, res, next) => {
console.log('Called before any other route handlers');
req.foo = 'Foo was here!';
next(); // must be called to continue
});
app.get('/foo', function (req, res) {
// Custom middleware result
console.log('Foo: ', req.foo);
res.send('Done');
});
export default app.init();
```
## app.get(route, workerFunction)
Execute workerFunction function on HTTP GET method and route match.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
app.get('/foo', function (req, res) {
res.send('Done');
});
export default app.init();
```
## app.post(route, workerFunction)
Execute workerFunction function on HTTP POST method and route match.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
app.post('/foo', function (req, res) {
console.log('Data from client is', req.body);
res.send('Done');
});
export default app.init();
```
## app.put(route, workerFunction)
Execute workerFunction function on HTTP PUT method and route match.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
app.put('/foo', function (req, res) {
console.log('Data from client is', req.body);
res.send('Done');
});
export default app.init();
```
## app.patch(route, workerFunction)
Execute workerFunction function on HTTP PATCH method and route match.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
app.patch('/foo', function (req, res) {
console.log('Data from client is', req.body);
res.send('Done');
});
export default app.init();
```
## app.delete(route, workerFunction)
Execute workerFunction function on HTTP DELETE method and route match.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
app.delete('/foo/:ID', function (req, res) {
const { ID } = req.params;
console.log('Delete this', ID);
res.send('Done');
});
export default app.init();
```
## app.all(route, workerFunction)
If no other routes matched before this, then execute the workerFunction function on all HTTP methods and route match.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
```js
import { app } from 'codehooks-js';
app.get('/foo', function (req, res) {
res.send('Done GET');
});
app.all('/*', function (req, res) {
res.send(
`None of the above routes matched but this ${req.method} ${req.originalUrl}`
);
});
export default app.init();
```
## app.auth(route, workerFunction)
Execute workerFunction function when no authentication token (x-apikey or JWT) is present.
Works for all HTTP methods and route matches.
**Parameters**
- **route**: String or RegExp that [matches route](rest-api-app-routes#rest-api-route-matching)
- **workerFunction**: function([requestObject](rest-api-app-routes#the-rest-api-request-object), [responseObject](rest-api-app-routes#the-rest-api-response-object))
**Returns** void
**Code example**
[See Auth hooks docs here.](authhooks)
## app.worker(name, workerFunction)
Register a named workerFunction function that can be used to process queued events or scheduled events.
**Parameters**
- **name**: unique worker name
- **workerFunction**: function(queueData, responseFunction)
**Returns** void
**Code example for worker queues**
[See Queue docs here.](queuehooks)
**Code example for scheduled workers**
[See scheduled jobs docs here.](jobhooks#schedule-delayed-worker-function-dynamically-with-runat)
## app.job(cronExpression, workerFunction)
Execute workerFunction function when cron job are scheduled.
**Parameters**
- **cronExpression**: a valid cron expression string
- **workerFunction**: function(jobData, responseFunction)
[See Job hook docs here.](jobhooks)
## app.static(options, callback)
Serve static files from deployed source directory.
For example: `app.static({ route: '/img', directory: '/assets/images' })`
**Options**
- **directory**: path to file upload directory
- **route**: URL sub route for clients
**callback(req, res, next)**
Provide a middleware function to control the request.
For example, add client side cache headers instructions for static assets.
```js
// serve static assets/images, and cache for 1 hour
app.static({ route: '/assets', directory: '/assets' }, (req, res, next) => {
console.log(
'If you see this, the client cache is invalidated or called for the first time'
);
const ONE_HOUR = 1000 * 60 * 60;
res.set('Cache-Control', `public, max-age=${ONE_HOUR}, s-maxage=${ONE_HOUR}`);
res.setHeader('Expires', new Date(Date.now() + ONE_HOUR).toUTCString());
res.removeHeader('Pragma');
next();
});
```
## app.storage(options, callback)
Serve files from the blob storage file system. You can upload files to the blob storage with the CLI.
Also see the [Filesystem API docs](/docs/fileapi).
```bash
$ coho file-upload --projectname 'myproject-fafc' --space 'dev' --src '/mylocaldir/' --target '/mydocuments'
```
For example: `app.storage({ route: '/docs', directory: '/mydocuments' })`
**Options**
- **directory**: path to file upload directory
- **route**: URL sub route for clients
**callback(req, res, next)**
Provide a middleware function to control the request.
## app.crudlify(schema, options)
Creates a complete Database REST API ([detailed docs here](/docs/database-rest-api)).
Example: REST API for two collection `customer` and `product`, any schema is allowed:
```js
app.crudlify({ customer: {}, product: {} }, { prefix: '/crudapi' });
// Example valid URL's
// https://myproject-ff00.api.codehooks.io/dev/customer?name=Bill
// https://myproject-ff00.api.codehooks.io/dev/product?price>42
```
**Schema**
-- **collection**: schema, for specific collections, e.g. `{"collection": someSchema}`
**Options**
- **prefix**: serve REST routes under another route. `{"prefix": "/api"}`
**Returns** Promise to `before` `after` functions.
Example:
```js
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
})
```
## app.createWorkflow(name, desc, workflowJSON)
Create reliable stateful workflows using plain JavaScript.
**Parameters**
- **name** (string): Unique identifier for the workflow
- **description** (string): Human-readable description of the workflow
- **steps** (WorkflowDefinition): Object containing step definitions
**Returns**: Workflow instance for managing the workflow
**Code example**:
```js
import { app } from 'codehooks-js';
// The workflow definition
const workflow = app.createWorkflow('minimal', 'Minimal workflow example', {
start: (state, goto) => {
goto('end', { ...state, message: 'Hello World' });
},
end: (state, goto) => goto(null, state), // null complete the workflow
});
// A REST API to start a new workflow instance
app.post('/start', async (req, res) =>
res.json(await workflow.start({ prop: 'some value' }))
);
export default app.init();
```
Read more in the [detailed workflow API docs](/docs/workflow-api)
---
## Authentication
*(Note: This content contains MDX/JSX code)*
Application APIs and data can be accessed with a secure token.
Tokens can be JWT tokens created from a provider such as Auth0.com or API tokens created by yourself (or any project user with ADMIN rights).
In addition, some CLI commands can use a special _admin token_ (se below).
## App-to-app authentication with API tokens
### Create an API token with the CLI
API tokens can be created i two ways.
1. Using the CLI command `coho add-token`
2. Using the admin application at https://account.codehooks.io
The example below shows how to use the CLI to create a new `READ` access API token.
```bash
coho add-token --readonly
Created new token 5f1ce782-5798-4295-bdd0-51568390a59c with access READ
```
The API token can now be used to access your application API.
Add the `x-apikey` as a HTTP header in the request.
```bash
curl https://myproject-ff00.api.codehooks.io/dev/myapi \
-H 'x-apikey: 5f1ce782-5798-4295-bdd0-51568390a59c'
```
:::warning note
The secure token should not be used from web pages. We recommend that you only use it server side.
:::
### Using the web application to add an API token
In your account at https://account.codehooks.io, find the project and the space settings to add a new token.

## Authenticate users with JWT (using JWKS)
### Set up JWKS with the CLI
If you have set up JWT authentication using [Auth0.com (Okta)](https://auth0.com), [Clerk](https://clerk.com/), [Stack-Auth](https://stack-auth.com) or similar services, you can easily set up a JWKS endpoint in the space settings. All API calls will then be verified using this mechanism.
```bash
# add JWKS endpoint
$ coho jwks https://.auth0.com/.well-known/jwks.json
Set JWKS url to space 'dev'
```
| Auth provider | JWKS URL |
| -------------- | ----------------------------------------------------------------------------------------------- |
| Auth0.com | `https://.auth0.com/.well-known/jwks.json` |
| Clerk.com | `https:///.well-known/jwks.json` |
| Stack-auth.com | `https://api.stack-auth.com/api/v1/projects//.well-known/jwks.json` |
### Using the web application to set a JWKS endpoint
In your account at https://account.codehooks.io, find the project and the space settings to set the JWKS endpoint URL.

:::tip
You can also roll your own authentication using code. Read more about using [authhooks](/docs/authhooks) for this.
:::
:::info Verifying JWT tokens - RS256 vs HS256
By default, to verify login tokens (JWT), Auth0 example applications now use the asymmetric RS256 algorithm instead of the HS256 symmetric one. What is the difference? HS256 requires you to use a secret key to verify the token but RS256 let you use a public key which can be fetched from an URL on the web (JWKS URL). You can read more about it in [this blogpost by Auth0.](https://auth0.com/blog/navigating-rs256-and-jwks/)
:::
## Use admin tokens to authenticate with the CLI
For use with the CLI, you can create admin tokens instead of having to log in to your account as a user with ADMIN rights. Admin tokens belong to the personal account or a team and applies to the following codehooks CLI commands: deploy, undeploy, createindex, removeindex, backup, restore, import, export, query.
Admin tokens are ideal for use in shell scripts and for CI purposes.
```bash
# add admin token
$ coho add-admintoken
? Select personal account or team to add admin token to Personal
New admin token added: 0882e570d8fc7fe97ae958ae8df3d7ba-7f5c5eb7177c
Please copy this now and keep it secret. It will never be shown in full again.
```
```bash
# use the admin token
$ coho deploy --admintoken 0882e570d8fc7fe97ae958ae8df3d7ba-7f5c5eb7177c
Deploying to Project: students-3bfe Space: dev
Deployed Codehook successfully! π
```
## Use IP address to limit API access
From your serverless functions you can inspect the client IP address from the request header fields.
The example headers listen below shows that the client IP address is `11.22.33.44`.
```js
headers: {
host: 'myapp-f0gt.api.codehooks.io',
'x-request-id': 'ba5582e3bafd12602b3f70a5af5ab20f',
'x-real-ip': '11.22.33.44',
'x-forwarded-for': '11.22.33.44',
'x-forwarded-host': 'myapp-f0gt.api.codehooks.io',
'x-forwarded-port': '443',
'x-forwarded-proto': 'https',
'x-forwarded-scheme': 'https',
'x-scheme': 'https',
accept: '*/*',
'user-agent': 'Thunder Client (https://www.thunderclient.com)',
'x-api-key': '07ee0bba-xxxx-yyy-zzzz-dfd0b991fa0a'
}
```
Use the [CLI command `coho whitelist`](/docs/cli.md#whitelist) or the admin web application to give exclusive IP addresses API access, e.g.
`coho whitelist 11.22.33.44`
You can use a [authentication hook](/docs/authhooks) to intercept API requests.
## Custom Authentication with codehooks-auth
For complete control over authentication, you can use the open-source [`codehooks-auth`](https://github.com/RestDB/codehooks-auth) package. This solution provides:
- JWT-based authentication with access and refresh tokens
- Built-in user management with OAuth support (Google and GitHub)
- Email verification and OTP (One-Time Password) authentication
- Customizable templates and email notifications

### Setup Instructions
1. Install the required packages:
```bash
npm install codehooks-auth codehooks-js
```
2. Set up JWT secrets:
```bash
coho set-env JWT_ACCESS_TOKEN_SECRET 'your_access_token_secret' --encrypted
coho set-env JWT_REFRESH_TOKEN_SECRET 'your_refresh_token_secret' --encrypted
```
3. Configure authentication in your backend code:
```js
import { app } from 'codehooks-js';
import { initAuth } from 'codehooks-auth';
const settings = {
JWT_ACCESS_TOKEN_SECRET: process.env.JWT_ACCESS_TOKEN_SECRET,
JWT_REFRESH_TOKEN_SECRET: process.env.JWT_REFRESH_TOKEN_SECRET,
redirectSuccessUrl: '/dashboard.html', // where to redirect after successful login
baseAPIRoutes: '/api', // protected routes
// Optional: Configure OAuth providers
google: {
CLIENT_ID: process.env.CLIENT_ID,
CLIENT_SECRET: process.env.CLIENT_SECRET,
REDIRECT_URI:
'https://{YOUR_APP_URL}.codehooks.io/auth/oauthcallback/google',
},
github: {
CLIENT_ID: process.env.GITHUB_CLIENT_ID,
CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
REDIRECT_URI:
'https://{YOUR_APP_URL}.codehooks.io/auth/oauthcallback/github',
},
};
// Initialize auth with your settings
initAuth(app, settings);
export default app.init();
```
4. Access protected routes in your client code:
```js
fetch('/api/protected-route', {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
});
```
The authentication system will automatically handle:
- User registration and login flows
- JWT token management (access and refresh tokens)
- OAuth authentication with supported providers
- Email verification and password reset functionality
- Protected API route authorization
See the [codehooks-auth documentation](https://github.com/RestDB/codehooks-auth) for complete setup instructions, template customization options, and advanced configuration.
---
## Auth hooks
*(Note: This content contains MDX/JSX code)*
Auth hooks lets you override the default security behaviour on specific routes.
If an auth hook route matches a route hook, and no authentication token is present in the headers, the auth function is called as a last resort to allow or dismiss the request.
The auth hook function logic calls `next()` to allow, or `response.end()` to block a client request.
Use the auth hook to create public routes and custom rules and overrides to fit any use case.
## Example auth hook
```javascript {9} title="index.js"
import app from 'codehooks-js'; // Standard JS lib for express style code
// REST API routes
app.get('/specialroute/frags', (req, res) => {
res.end('You have the correct secret header value');
});
// Auth hook
app.auth('/specialroute/*', (req, res, next) => {
// call some auth function here, e.g. myLookup
myLookup(req.headers['X-challenge'], (err, data) => {
if (err) {
res.status(401); // Unauthorized
res.end();
} else {
// allow API call
next();
}
});
});
function myLookup(challenge, callback) {
if (challenge === 'SOMESECRET') {
callback(null);
} else {
callback('Sorry');
}
}
export default app.init(); // Bind functions to the serverless runtime
```
[See docs for route matching](rest-api-app-routes#route-matching)
[See docs for middleware functions and the next() function](rest-api-app-routes#middleware-functions)
---
## Codehooks CLI tool
*(Note: This content contains MDX/JSX code)*
Command line interface (CLI) for codehooks.io ([npm package](https://www.npmjs.com/package/codehooks)).
**Version**: `1.2.20`
The CLI lets you manage projects and spaces (environments) (serverless functions + datastore + settings) from the command line.
## Popular commands
- New Project: `coho create myproject`
- Start coding: `mkdir myproject && cd myproject && coho init myproject-f2a0 --empty`
- Show project info: `coho info`
- New Space (environment): `coho add myspace`
- New Collection: `coho createcoll 'customers'`
- Import data: `coho import -f '/home/myfile.csv' -c 'customers' --separator ','`
- Invite: `coho invite jane@example.com myproject`
- Deploy your app: `coho deploy`
- Real time logs: `coho logs -f`
- Query the datastore: `coho query --collection products --query 'type=soft'`
- Upload static files: `coho upload './assets'`
## Install
```sh
$ npm i codehooks -g
```
## Usage
```sh
$ codehooks --help
```
Help output:
```
codehooks
Commands:
codehooks admin Open the codehooks.io admin account UI at account.codehooks.io
codehooks docs Open the codehooks.io documentation
codehooks login [provider] Authenticate CLI - sign up and/or sign in
codehooks logout Log out of the CLI
codehooks account Show info about account, projects and invitations
codehooks invite [email] [projectname] Invite user to project
codehooks join [projectname] Join project
codehooks leave [projectname] Leave project
codehooks create [projectname] [teamid] [description] Create and initialize a new codehooks project [aliases: init]
codehooks init [projectname] [space] Initialise an existing project space in the current folder. Sets up default CRUD template in index.js.
codehooks add [space] [projectname] [restricted] Add new space to project
codehooks use [name] Set active space
codehooks info [projectname] [space] Show info about project and spaces
codehooks stats [project] Show usage metrics for project spaces
codehooks file-upload [src] [target] Upload files to server [aliases: up, upload]
codehooks file-delete [filename] Delete a file from server [aliases: delete]
codehooks file-list [path] List files from server [aliases: files]
codehooks verify [dir] Compile code (defaults to current dir) [aliases: compile, ver, comp]
codehooks deploy Deploys current codehook folder [aliases: de, dep]
codehooks undeploy Undeploy current codehook folder [aliases: unde, undep]
codehooks install [template] Install a template application [aliases: inst, i]
codehooks log [space] [tail] [follow] [context] Show system logs for a space. [aliases: logs]
codehooks count [collection] [space] Count objects in a collection in current space [aliases: co]
codehooks query [collection] [query] Run query on collection in current space [aliases: q]
codehooks createindex [collection] [index] [space] Add field(s) to a query index [aliases: index, idx, create-index]
codehooks dropindex [collection] [index] [space] Remove field(s) from a query index [aliases: removeindex, remove-index, rmindex, delete-index]
codehooks get [key] [keyspace] [space] [text] [jsonl] [onlycount] Retrieve key-value pair(s) from a space
codehooks set [key] [val] Set key-value pair in a space
codehooks del [key] [keyspace] [space] [json] Delete key-value pair in a space
codehooks collection [project] [output] Show collections for space [aliases: coll, col, ls]
codehooks createcollection [collection] Create a new collection [aliases: createcoll, add-collection]
codehooks dropcollection [collection] Delete all data in collection and remove collection name [aliases: dropcoll, rmcoll, deletecoll]
codehooks add-schema [collection] [schema] Add a JSON schema to a collection [aliases: schema, create-schema]
codehooks remove-schema [collection] Remove JSON schema for a collection [aliases: delete-schema, del-schema]
codehooks cap-collection [collection] [cap] [capdelay] Cap a collection [aliases: cap, cap-coll, capcoll]
codehooks uncap-collection [collection] Remove cap on a collection [aliases: uncap]
codehooks import [filepath] [collection] [project] [space] [dryrun] Import JSON or CSV data from file [aliases: imp]
codehooks export [collection] [project] [space] Export JSON or CSV data
codehooks add-token [readonly] Add token to space
codehooks remove-token [token] Remove token from space
codehooks set-env [key] [value] [encrypted] Set environment variable for space
codehooks remove-env [key] Remove environment variable from space
codehooks jwks [url] Set/replace JWKS endpoint for OAuth2 authentication. Set to "" (empty string) to remove.
codehooks whitelist [ip] Add host to whitelist (use to restrict space access)
codehooks whitelist-remove [ip] Remove host from whitelist
codehooks completion Generate command completion script. Just add this to your .bashrc, .bash_profile, .zshrc (or similar) on *nix machines
codehooks remove-project [projectname] Remove the project
codehooks remove-space [space] [projectname] Remove space and data
codehooks admintokens Show active admintokens for account or team
codehooks add-admintoken Add admin token to account or team (for use with CI)
codehooks remove-admintoken Remove admin token from account or team
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
No project active in this folder! Use 'coho create' or 'codehooks create' to create a new project.
created by Codehooks AS - info@codehooks.io
***DEVELOPMENT***
```
## Available commands
- [admin](#admin)
- [docs](#docs)
- [login](#login)
- [logout](#logout)
- [account](#account)
- [invite](#invite)
- [join](#join)
- [leave](#leave)
- [create](#create)
- [init](#init)
- [add](#add)
- [use](#use)
- [info](#info)
- [stats](#stats)
- [file-upload](#file-upload)
- [file-delete](#file-delete)
- [file-list](#file-list)
- [verify](#verify)
- [deploy](#deploy)
- [undeploy](#undeploy)
- [install](#install)
- [log](#log)
- [count](#count)
- [query](#query)
- [createindex](#createindex)
- [dropindex](#dropindex)
- [get](#get)
- [set](#set)
- [del](#del)
- [collection](#collection)
- [createcollection](#createcollection)
- [dropcollection](#dropcollection)
- [add-schema](#add-schema)
- [remove-schema](#remove-schema)
- [cap-collection](#cap-collection)
- [uncap-collection](#uncap-collection)
- [import](#import)
- [export](#export)
- [add-token](#add-token)
- [remove-token](#remove-token)
- [set-env](#set-env)
- [remove-env](#remove-env)
- [jwks](#jwks)
- [whitelist](#whitelist)
- [whitelist-remove](#whitelist-remove)
- [completion](#completion)
- [remove-project](#remove-project)
- [remove-space](#remove-space)
- [admintokens](#admintokens)
- [add-admintoken](#add-admintoken)
- [remove-admintoken](#remove-admintoken)
### admin
```sh
$ codehooks admin --help
```
Help output:
```
codehooks admin
Open the codehooks.io admin account UI at account.codehooks.io
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### docs
```sh
$ codehooks docs --help
```
Help output:
```
codehooks docs
Open the codehooks.io documentation
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### login
```sh
$ codehooks login --help
```
Help output:
```
codehooks login [provider]
Authenticate CLI - sign up and/or sign in
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### logout
```sh
$ codehooks logout --help
```
Help output:
```
codehooks logout
Log out of the CLI
Options:
-d, --debug show debug (verbose) information
--dir [default: "."]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### account
```sh
$ codehooks account --help
```
Help output:
```
codehooks account
Show info about account, projects and invitations
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### invite
```sh
$ codehooks invite --help
```
Help output:
```
codehooks invite [email] [projectname]
Invite user to project
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
-t, --email [string] [required]
--role [string] [default: "ADMIN"]
--remove remove invitation [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### join
```sh
$ codehooks join --help
```
Help output:
```
codehooks join [projectname]
Join project
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### leave
```sh
$ codehooks leave --help
```
Help output:
```
codehooks leave [projectname]
Leave project
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### create
```sh
$ codehooks create --help
```
Help output:
```
codehooks create [projectname] [teamid] [description]
Create and initialize a new codehooks project
Options:
-d, --debug show debug (verbose) information
--description A few words about this project
-n, --projectname Project name
-t, --teamid Add project to team with this id
-g, --ga-deploy Add Github Action for automatic deploy [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### init
```sh
$ codehooks init --help
```
Help output:
```
codehooks init [projectname] [space]
Initialise an existing project space in the current folder. Sets up default CRUD template in index.js.
Options:
-d, --debug show debug (verbose) information
-n, --projectname Project name
-s, --space Space (environment) name [default: "dev"]
-e, --empty Only create the project config file [boolean]
--download Download source code from project [boolean] [default: true]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks init
codehooks init --empty
codehooks init --download
```
### add
```sh
$ codehooks add --help
```
Help output:
```
codehooks add [space] [projectname] [restricted]
Add new space to project
Options:
-d, --debug show debug (verbose) information
--projectname [required]
-n, --space space name [required]
--restricted only team admins or owner can use if restricted [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### use
```sh
$ codehooks use --help
```
Help output:
```
codehooks use [name]
Set active space
Options:
-d, --debug show debug (verbose) information
--projectname [required]
-n, --name
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### info
```sh
$ codehooks info --help
```
Help output:
```
codehooks info [projectname] [space]
Show info about project and spaces
Options:
-d, --debug show debug (verbose) information
--projectname Active project [required]
--json Output info as JSON (not table) [boolean]
--space Only show info for this space [string]
-e, --examples Include cURL examples [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### stats
```sh
$ codehooks stats --help
```
Help output:
```
codehooks stats [project]
Show usage metrics for project spaces
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to query
--space Only show info for this space [string]
--json Output info as json [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### file-upload
```sh
$ codehooks file-upload --help
```
Help output:
```
codehooks file-upload [src] [target]
Upload files to server
Options:
-d, --debug show debug (verbose) information
-f, --src Path to directory or file [default: "."]
-t, --target Target directory on server, same as local if not set
-e, --ext Include files matching the extension [string]
--space Select which space (environment) to access
-p, --projectname Select which project name to use
--admintoken Use admin token authentication (use for CI)
--dryrun Output files to upload without performing the action [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks file-upload './static'
codehooks file-upload --src './local' --target '/remote'
codehooks file-upload --src './local' --target '/remote' --ext 'png'
codehooks file-upload --src './local' --target '/remote' --ext 'png|jpg|jpeg|gif'
codehooks file-upload afile.txt
```
### file-delete
```sh
$ codehooks file-delete --help
```
Help output:
```
codehooks file-delete [filename]
Delete a file from server
Options:
-d, --debug show debug (verbose) information
-f, --filename Delete file with match on absolute path/filename
-r, --match Delete multiple file that matches regular expression to a file path
--space Select which space (environment) to access
-p, --projectname Select which project name to use
--admintoken Use admin token authentication (use for CI)
--dryrun Output files to upload without performing the action [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks file-delete --filename '/static/myfile.txt'
codehooks file-delete --match '/static/*'
codehooks file-delete --match '.css$'
```
### file-list
```sh
$ codehooks file-list --help
```
Help output:
```
codehooks file-list [path]
List files from server
Options:
-d, --debug show debug (verbose) information
-f, --path Path to directory or file [default: "."]
--space Select which space (environment) to access
-p, --project Select which project name to use
--admintoken Use admin token authentication (use for CI)
--reverse Scan index in reverse order
--table Output data as formatted table (not JSON) [boolean]
--json Output data as formatted JSON [boolean]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks file-list '/static/'
```
### verify
```sh
$ codehooks verify --help
```
Help output:
```
codehooks verify [dir]
Compile code (defaults to current dir)
Options:
-d, --debug show debug (verbose) information
--dir [default: "."]
--space Select which space to access
-p, --projectname Select which project name to use
--admintoken Use admin token authentication (use for CI)
-e, --main Application main file, default is index.js [default: "index"]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### deploy
```sh
$ codehooks deploy --help
```
Help output:
```
codehooks deploy
Deploys current codehook folder
Options:
-d, --debug show debug (verbose) information
--dir [default: "."]
-s, --space Select which space to access
-p, --projectname Select which project name to use
--history Show deployment history
--rollback Undo last deployment, set previous as active
--json Output JSON format
--admintoken Use admin token authentication (use for CI)
--template Deploy a pre defined code template
--upload Upload source code assets to codehooks.io this projects environment [boolean] [default: true]
--compress Compress source code assets before upload [boolean] [default: true]
-e, --main Application main file, default is index.js [default: "index"]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### undeploy
```sh
$ codehooks undeploy --help
```
Help output:
```
codehooks undeploy
Undeploy current codehook folder
Options:
-d, --debug show debug (verbose) information
--dir [default: "."]
-s, --space Select which space to access
-p, --projectname Select which project name to use
--json Output JSON format
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### install
```sh
$ codehooks install --help
```
Help output:
```
codehooks install [template]
Install a template application
Options:
-d, --debug show debug (verbose) information
-t, --template Template directory from Github repo with templates [string] [required]
--space Select which space (environment) to access
-p, --projectname Select which project name to use
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks install 'static-website-tailwincss'
```
### log
```sh
$ codehooks log --help
```
Help output:
```
codehooks log [space] [tail] [follow] [context]
Show system logs for a space.
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project name to use
-s, --space Select which space to log
-t, --tail Chop log to n lines [default: 100]
-f, --follow Keep log stream open
-c, --context Filter log on: jobhooks, queuehooks, routehooks, datahooks, auth
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks log
codehooks log -f
codehooks coho log --tail 10
coho log --project 'pets-ff00' --space prod
```
### count
```sh
$ codehooks count --help
```
Help output:
```
codehooks count [collection] [space]
Count objects in a collection in current space
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to query
-s, --space Select which space to query
-c, --collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### query
```sh
$ codehooks query --help
```
Help output:
```
codehooks query [collection] [query]
Run query on collection in current space
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to query
-s, --space Select which space to query
-q, --query Limit selection with a query expression or JSON string '{...}' [default: ""]
-n, --count Count query results
-c, --collection Collection name [required]
--delete Delete all items from query result
--update Patch all items from query result with JSON string '{...}' [string]
--replace Replace all items from query result with JSON string '{...}' [string]
--useindex Use an indexed field to scan data in query [string]
--start Start value for index scan [string]
--end End value for index scan [string]
--limit Limit query result [number]
--fields Comma separated list of fields to include [string]
--sort Comma separated list of fields to sort by [string]
--offset Skip items before returning data in query result [number]
--enqueue Add query result to queue topic [string]
--pretty Output data with formatting and colors
--reverse Scan index in reverse order
--table Output data as formatted table (not JSON) [boolean]
--csv Output data in CSV format [boolean]
--jsonl Output as JSON Lines (one JSON object per line) [boolean]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks query pets name=Polly
codehooks query --collection pets --query 'name=Polly&type=Parrot'
codehooks query --collection pets --query 'name=/^po/'
codehooks query --collection pets --query 'name=/^po/' --sort 'age,name'
codehooks query pets 'name=Polly' --useindex name --fields 'name,type'
codehooks q pets 'name=Polly&type=Parrot' --update '{"name": "Zilla"}'
codehooks q pets 'type=Fish' --delete
codehooks q pets 'type=Snake' --enqueue 'mytopic'
codehooks q pets --jsonl
codehooks query pets --q '{"type":"Dog"}' --project petsdb-5544 --space dev
```
### createindex
```sh
$ codehooks createindex --help
```
Help output:
```
codehooks createindex [collection] [index] [space]
Add field(s) to a query index
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
-c, --collection Collection with indexes [required]
-i, --index Field to index [required]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks createindex --collection pets --index name
codehooks idx pets name
codehooks idx pets -i name -i type -i 'price-in-dollar'
```
### dropindex
```sh
$ codehooks dropindex --help
```
Help output:
```
codehooks dropindex [collection] [index] [space]
Remove field(s) from a query index
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
-c, --collection Collection with indexes [required]
-i, --index Field to remove index for [required]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks removeindex pets name
codehooks removeindex --collection pets --index 'name' --index 'type'
```
### get
```sh
$ codehooks get --help
```
Help output:
```
codehooks get [key] [keyspace] [space] [text] [jsonl] [onlycount]
Retrieve key-value pair(s) from a space
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to query
-k, --key Key to match, or key* to fetch list [default: "*"]
--keyspace Keyspace to scan
--text Output info as text line [boolean]
--jsonl Output as JSON Lines (one JSON object per line) [boolean]
--onlycount Output only the count of keys [boolean]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks get 'my-value-one'
codehooks get 'my*'
codehooks get '*' --keyspace spacex
codehooks get 'my*' --text
codehooks get 'my*' --jsonl
codehooks get 'my*' --onlycount
```
### set
```sh
$ codehooks set --help
```
Help output:
```
codehooks set [key] [val]
Set key-value pair in a space
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use [string]
-s, --space Select which space to use [string]
--key Key to set [string] [required]
--val Value to set [string] [required]
--keyspace Keyspace to use [string]
--ttl Time to live in millis for value [number]
--json Output info as JSON [boolean]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks set 'my-value-one' 'foo'
codehooks set 'my-value-two' 'bar'
codehooks set 'session-4f51-9bed' 'OK' --keyspace 'spacex' --ttl 60000
```
### del
```sh
$ codehooks del --help
```
Help output:
```
codehooks del [key] [keyspace] [space] [json]
Delete key-value pair in a space
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
--key Key to delete [required]
--keyspace Keyspace to use
--json Output info as JSON [boolean]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks del 'my-value-one'
codehooks del 'my-value-two' --keyspace 'spacex'
```
### collection
```sh
$ codehooks collection --help
```
Help output:
```
codehooks collection [project] [output]
Show collections for space
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to query
-s, --space Select which space to query
--json JSON output format
--sys Show system collections [boolean]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### createcollection
```sh
$ codehooks createcollection --help
```
Help output:
```
codehooks createcollection [collection]
Create a new collection
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
--collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
--cap Cap collection to max documents
--capdelay Delay capping to millis
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks createcollection pets
codehooks createcollection logs --cap 5000
```
### dropcollection
```sh
$ codehooks dropcollection --help
```
Help output:
```
codehooks dropcollection [collection]
Delete all data in collection and remove collection name
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
-c, --collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks dropcollection pets
```
### add-schema
```sh
$ codehooks add-schema --help
```
Help output:
```
codehooks add-schema [collection] [schema]
Add a JSON schema to a collection
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
--collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
--schema Path to file with JSON schema for collection [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks add-schema --collection 'person' --schema './personSchema.json'
```
### remove-schema
```sh
$ codehooks remove-schema --help
```
Help output:
```
codehooks remove-schema [collection]
Remove JSON schema for a collection
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
--collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks remove-schema --collection 'person'
```
### cap-collection
```sh
$ codehooks cap-collection --help
```
Help output:
```
codehooks cap-collection [collection] [cap] [capdelay]
Cap a collection
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
--collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
--cap Cap collection to max documents [default: 1000]
--capdelay Delay capping to max millis [default: 1000]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks capcollection --collection 'temperature' --cap 10000
```
### uncap-collection
```sh
$ codehooks uncap-collection --help
```
Help output:
```
codehooks uncap-collection [collection]
Remove cap on a collection
Options:
-d, --debug show debug (verbose) information
-p, --project Select which project to use
-s, --space Select which space to use
--collection Collection name [required]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
codehooks uncap-collection --collection 'temperature'
```
### import
```sh
$ codehooks import --help
```
Help output:
```
codehooks import [filepath] [collection] [project] [space] [dryrun]
Import JSON or CSV data from file
Options:
-d, --debug show debug (verbose) information
-p, --project Project name [string] [required]
-s, --space A space in your project [string]
-c, --collection A collection in a Space [string] [required]
-f, --filepath File to import [string] [required]
--separator Field separator character, default is ',', also normal with '\t' or ';' [string] [default: ","]
--dryrun Test only, will not import any data [boolean] [default: false]
--rowcount Add a row count field to each imported record [boolean] [default: false]
--admintoken Use admin token authentication (use for CI)
--local Import data to local development server on port parameter [number]
--encoding String encoding to use, latin1, utf8, ascii, hex, ucs2 [string] [default: "utf8"]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
# shorthand
codehooks import ./myfile.csv mycollection
# explicit parameters
codehooks import --filepath ./myfile.csv --collection mycollection
# parameter shortcuts
codehooks import -f ./myfile.json -c mycollection
# Import CSV data with custom separator and encoding
codehooks import -f ./myfile.csv -c mycollection --separator ';' --encoding 'latin1'
```
### export
```sh
$ codehooks export --help
```
Help output:
```
codehooks export [collection] [project] [space]
Export JSON or CSV data
Options:
-d, --debug show debug (verbose) information
-p, --project Project name [string] [required]
-s, --space a space name in your project [string]
--collection A collection in the space [string] [required]
-f, --filepath Filename to save export data [string]
--csv Export to CSV [boolean]
--jsonl Export to JSONL (JSON Lines) [boolean]
--admintoken Use admin token authentication (use for CI)
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### add-token
```sh
$ codehooks add-token --help
```
Help output:
```
codehooks add-token [readonly]
Add token to space
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
--readonly [boolean] [default: false]
--description [string] [default: ""]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### remove-token
```sh
$ codehooks remove-token --help
```
Help output:
```
codehooks remove-token [token]
Remove token from space
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
-t, --token [string] [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### set-env
```sh
$ codehooks set-env --help
```
Help output:
```
codehooks set-env [key] [value] [encrypted]
Set environment variable for space
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
--encrypted [boolean] [default: false]
--key [string] [required]
--value [string] [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### remove-env
```sh
$ codehooks remove-env --help
```
Help output:
```
codehooks remove-env [key]
Remove environment variable from space
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
-k, --key [string] [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### jwks
```sh
$ codehooks jwks --help
```
Help output:
```
codehooks jwks [url]
Set/replace JWKS endpoint for OAuth2 authentication. Set to "" (empty string) to remove.
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
--url URL of JWKS endpoint (must be https) [string] [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### whitelist
```sh
$ codehooks whitelist --help
```
Help output:
```
codehooks whitelist [ip]
Add host to whitelist (use to restrict space access)
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
--ip IP address which should be allowed access to space [string] [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### whitelist-remove
```sh
$ codehooks whitelist-remove --help
```
Help output:
```
codehooks whitelist-remove [ip]
Remove host from whitelist
Options:
-d, --debug show debug (verbose) information
--projectname Project name [required]
--space A space in your project [required]
--ip The IP address to remove from the whitelist. [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### completion
```sh
$ codehooks completion --help
```
Help output:
```
codehooks completion
Generate command completion script. Just add this to your .bashrc, .bash_profile, .zshrc (or similar) on *nix machines
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### remove-project
```sh
$ codehooks remove-project --help
```
Help output:
```
codehooks remove-project [projectname]
Remove the project
Options:
-d, --debug show debug (verbose) information
-p, --projectname Project name [string] [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### remove-space
```sh
$ codehooks remove-space --help
```
Help output:
```
codehooks remove-space [space] [projectname]
Remove space and data
Options:
-d, --debug show debug (verbose) information
--projectname Project name
--space A space in your project [required]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### admintokens
```sh
$ codehooks admintokens --help
```
Help output:
```
codehooks admintokens
Show active admintokens for account or team
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### add-admintoken
```sh
$ codehooks add-admintoken --help
```
Help output:
```
codehooks add-admintoken
Add admin token to account or team (for use with CI)
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
### remove-admintoken
```sh
$ codehooks remove-admintoken --help
```
Help output:
```
codehooks remove-admintoken
Remove admin token from account or team
Options:
-d, --debug show debug (verbose) information
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
```
## License
MIT.
---
## Client code examples
*(Note: This content contains MDX/JSX code)*
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
Client applications communicate with a Codehooks API and database via a secure REST API.
You can interface with Codehooks from most popular programming languages and platforms, such as: cURL, JavaScript, Python, PHP, Java, C#, Kotlin, R and Swift.
In the code examples shown below we use `mystore-fafb` as an example Codehooks project name and `dev` as a project data space. Replace this with your own project name, and use a valid API token (`x-apikey`) or a JWT/JWKS integration (`Bearer `).
## Code examples for popular programming languages
`GET` documents from the salesorder collection.
```bash
curl --location 'https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
`POST` a new document to the salesorder collection.
```bash
curl --location 'https://mystore-fafb.api.codehooks.io/dev/salesorder' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149' \
--header 'Content-Type: application/json' \
--data '{
"customerID": 1234,
"productID": 21435465,
"quantity": 3,
"payment": "subscription"
}'
```
`PATCH` (update) an existing document in the salesorder collection.
```bash
curl --location --request PATCH 'https://mystore-fafb.api.codehooks.io/dev/salesorder/9876' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149' \
--header 'Content-Type: application/json' \
--data '{
"quantity": 2,
"payment": "card"
}'
```
`DELETE` a document in the salesorder collection.
```bash
curl --location --request DELETE 'https://mystore-fafb.api.codehooks.io/dev/salesorder/9876' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
`GET` documents from the salesorder collection.
```js
var myHeaders = new Headers();
myHeaders.append('x-apikey', '3c932310-3fab-4ba3-8102-b75ba0f05149');
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow',
};
fetch(
'https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234',
requestOptions
)
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log('error', error));
```
`POST` a new document to the salesorder collection.
```js
var myHeaders = new Headers();
myHeaders.append('x-apikey', '3c932310-3fab-4ba3-8102-b75ba0f05149');
myHeaders.append('Content-Type', 'application/json');
var raw = JSON.stringify({
customerID: 1234,
productID: 21435465,
quantity: 3,
payment: 'subscription',
});
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow',
};
fetch('https://mystore-fafb.api.codehooks.io/dev/salesorder', requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.log('error', error));
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```js
var myHeaders = new Headers();
myHeaders.append('x-apikey', '3c932310-3fab-4ba3-8102-b75ba0f05149');
myHeaders.append('Content-Type', 'application/json');
var raw = JSON.stringify({
quantity: 2,
payment: 'card',
});
var requestOptions = {
method: 'PATCH',
headers: myHeaders,
body: raw,
redirect: 'follow',
};
fetch(
'https://mystore-fafb.api.codehooks.io/dev/salesorder/9876',
requestOptions
)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.log('error', error));
```
`DELETE` a document in the salesorder collection.
```js
var myHeaders = new Headers();
myHeaders.append('x-apikey', '3c932310-3fab-4ba3-8102-b75ba0f05149');
var requestOptions = {
method: 'DELETE',
headers: myHeaders,
redirect: 'follow',
};
fetch(
'https://mystore-fafb.api.codehooks.io/dev/salesorder/9876',
requestOptions
)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.log('error', error));
```
`GET` documents from the salesorder collection.
```py title="Using http.client"
import requests
url = "https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234"
payload = {}
headers = {
'x-apikey': '3c932310-3fab-4ba3-8102-b75ba0f05149'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
```
`POST` a new document to the salesorder collection.
```py
import http.client
import json
conn = http.client.HTTPSConnection("mystore-fafb.api.codehooks.io")
payload = json.dumps({
"customerID": 1234,
"productID": 21435465,
"quantity": 3,
"payment": "subscription"
})
headers = {
'x-apikey': '3c932310-3fab-4ba3-8102-b75ba0f05149',
'Content-Type': 'application/json'
}
conn.request("POST", "/dev/salesorder", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```py
import http.client
import json
conn = http.client.HTTPSConnection("mystore-fafb.api.codehooks.io")
payload = json.dumps({
"quantity": 2,
"payment": "card"
})
headers = {
'x-apikey': '3c932310-3fab-4ba3-8102-b75ba0f05149',
'Content-Type': 'application/json'
}
conn.request("PATCH", "/dev/salesorder/9876", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
```
`DELETE` a document in the salesorder collection.
```py
import http.client
conn = http.client.HTTPSConnection("mystore-fafb.api.codehooks.io")
payload = ''
headers = {
'x-apikey': '3c932310-3fab-4ba3-8102-b75ba0f05149'
}
conn.request("DELETE", "/dev/salesorder/9876", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
```
`GET` documents from the salesorder collection.
```cs title="Using HttpClient"
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234");
request.Headers.Add("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149");
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
```
`POST` (create) a new document to the salesorder collection.
```cs
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://mystore-fafb.api.codehooks.io/dev/salesorder");
request.Headers.Add("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149");
var content = new StringContent("{\n \"customerID\": 1234,\n \"productID\": 21435465,\n \"quantity\": 3,\n \"payment\": \"subscription\"\n}", null, "application/json");
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```cs
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Patch, "https://mystore-fafb.api.codehooks.io/dev/salesorder/9876");
request.Headers.Add("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149");
var content = new StringContent("{\n \"quantity\": 2,\n \"payment\": \"card\"\n}", null, "application/json");
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
```
`DELETE` a document in the salesorder collection.
```cs
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Delete, "https://mystore-fafb.api.codehooks.io/dev/salesorder/9876");
request.Headers.Add("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149");
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
```
`GET` documents from the salesorder collection.
```php title="Using Guzzle"
'3c932310-3fab-4ba3-8102-b75ba0f05149'
];
$request = new Request('GET', 'https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234', $headers);
$res = $client->sendAsync($request)->wait();
echo $res->getBody();
```
`POST` (create) a new document to the salesorder collection.
```php
'3c932310-3fab-4ba3-8102-b75ba0f05149',
'Content-Type' => 'application/json'
];
$body = '{
"customerID": 1234,
"productID": 21435465,
"quantity": 3,
"payment": "subscription"
}';
$request = new Request('POST', 'https://mystore-fafb.api.codehooks.io/dev/salesorder', $headers, $body);
$res = $client->sendAsync($request)->wait();
echo $res->getBody();
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```php
'3c932310-3fab-4ba3-8102-b75ba0f05149',
'Content-Type' => 'application/json'
];
$body = '{
"quantity": 2,
"payment": "card"
}';
$request = new Request('PATCH', 'https://mystore-fafb.api.codehooks.io/dev/salesorder/9876', $headers, $body);
$res = $client->sendAsync($request)->wait();
echo $res->getBody();
```
`DELETE` a document in the salesorder collection.
```php
'3c932310-3fab-4ba3-8102-b75ba0f05149'
];
$request = new Request('DELETE', 'https://mystore-fafb.api.codehooks.io/dev/salesorder/9876', $headers);
$res = $client->sendAsync($request)->wait();
echo $res->getBody();
```
`GET` documents from the salesorder collection.
```java title="Using Unirest"
Unirest.setTimeouts(0, 0);
HttpResponse response = Unirest.get("https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234")
.header("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.asString();
```
`POST` (create) a new document to the salesorder collection.
```java
Unirest.setTimeouts(0, 0);
HttpResponse response = Unirest.post("https://mystore-fafb.api.codehooks.io/dev/salesorder")
.header("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.header("Content-Type", "application/json")
.body("{\n \"customerID\": 1234,\n \"productID\": 21435465,\n \"quantity\": 3,\n \"payment\": \"subscription\"\n}")
.asString();
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```java
Unirest.setTimeouts(0, 0);
HttpResponse response = Unirest.patch("https://mystore-fafb.api.codehooks.io/dev/salesorder/9876")
.header("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.header("Content-Type", "application/json")
.body("{\n \"quantity\": 2,\n \"payment\": \"card\"\n}")
.asString();
```
`DELETE` a document in the salesorder collection.
```java
Unirest.setTimeouts(0, 0);
HttpResponse response = Unirest.delete("https://mystore-fafb.api.codehooks.io/dev/salesorder/9876")
.header("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.asString();
```
`GET` documents from the salesorder collection.
```swift title="Using URLSession"
var request = URLRequest(url: URL(string: "https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234")!,timeoutInterval: Double.infinity)
request.addValue("3c932310-3fab-4ba3-8102-b75ba0f05149", forHTTPHeaderField: "x-apikey")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
print(String(data: data, encoding: .utf8)!)
}
task.resume()
```
`POST` (create) a new document to the salesorder collection.
```swift
let parameters = "{\n \"customerID\": 1234,\n \"productID\": 21435465,\n \"quantity\": 3,\n \"payment\": \"subscription\"\n}"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://mystore-fafb.api.codehooks.io/dev/salesorder")!,timeoutInterval: Double.infinity)
request.addValue("3c932310-3fab-4ba3-8102-b75ba0f05149", forHTTPHeaderField: "x-apikey")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = postData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
print(String(data: data, encoding: .utf8)!)
}
task.resume()
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```swift
let parameters = "{\n \"quantity\": 2,\n \"payment\": \"card\"\n}"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://mystore-fafb.api.codehooks.io/dev/salesorder/9876")!,timeoutInterval: Double.infinity)
request.addValue("3c932310-3fab-4ba3-8102-b75ba0f05149", forHTTPHeaderField: "x-apikey")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "PATCH"
request.httpBody = postData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
print(String(data: data, encoding: .utf8)!)
}
task.resume()
```
`DELETE` a document in the salesorder collection.
```swift
var request = URLRequest(url: URL(string: "https://mystore-fafb.api.codehooks.io/dev/salesorder/9876")!,timeoutInterval: Double.infinity)
request.addValue("3c932310-3fab-4ba3-8102-b75ba0f05149", forHTTPHeaderField: "x-apikey")
request.httpMethod = "DELETE"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
print(String(data: data, encoding: .utf8)!)
}
task.resume()
```
`GET` documents from the salesorder collection.
```kotlin title="Using Okhttp"
val client = OkHttpClient()
val request = Request.Builder()
.url("https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234")
.addHeader("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.build()
val response = client.newCall(request).execute()
```
`POST` (create) a new document to the salesorder collection.
```kotlin
val client = OkHttpClient()
val mediaType = "application/json".toMediaType()
val body = "{\n \"customerID\": 1234,\n \"productID\": 21435465,\n \"quantity\": 3,\n \"payment\": \"subscription\"\n}".toRequestBody(mediaType)
val request = Request.Builder()
.url("https://mystore-fafb.api.codehooks.io/dev/salesorder")
.post(body)
.addHeader("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.addHeader("Content-Type", "application/json")
.build()
val response = client.newCall(request).execute()
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```kotlin
val client = OkHttpClient()
val mediaType = "application/json".toMediaType()
val body = "{\n \"quantity\": 2,\n \"payment\": \"card\"\n}".toRequestBody(mediaType)
val request = Request.Builder()
.url("https://mystore-fafb.api.codehooks.io/dev/salesorder/9876")
.patch(body)
.addHeader("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.addHeader("Content-Type", "application/json")
.build()
val response = client.newCall(request).execute()
```
`DELETE` a document in the salesorder collection.
```kotlin
val client = OkHttpClient()
val mediaType = "text/plain".toMediaType()
val body = "".toRequestBody(mediaType)
val request = Request.Builder()
.url("https://mystore-fafb.api.codehooks.io/dev/salesorder/9876")
.method("DELETE", body)
.addHeader("x-apikey", "3c932310-3fab-4ba3-8102-b75ba0f05149")
.build()
val response = client.newCall(request).execute()
```
`GET` documents from the salesorder collection.
```r title="Using RCurl"
library(RCurl)
headers = c(
"x-apikey" = "3c932310-3fab-4ba3-8102-b75ba0f05149"
)
res <- getURL("https://mystore-fafb.api.codehooks.io/dev/salesorder?customerID=1234", .opts=list(httpheader = headers, followlocation = TRUE))
cat(res)
```
`POST` (create) a new document to the salesorder collection.
```r
library(RCurl)
headers = c(
"x-apikey" = "3c932310-3fab-4ba3-8102-b75ba0f05149",
"Content-Type" = "application/json"
)
params = "{
\"customerID\": 1234,
\"productID\": 21435465,
\"quantity\": 3,
\"payment\": \"subscription\"
}"
res <- postForm("https://mystore-fafb.api.codehooks.io/dev/salesorder", .opts=list(postfields = params, httpheader = headers, followlocation = TRUE), style = "httppost")
cat(res)
```
`PATCH` (update) an existing document (9876) in the salesorder collection.
```r
library(RCurl)
headers = c(
"x-apikey" = "3c932310-3fab-4ba3-8102-b75ba0f05149",
"Content-Type" = "application/json"
)
params = "{
\"quantity\": 2,
\"payment\": \"card\"
}"
res <- getURLContent("https://mystore-fafb.api.codehooks.io/dev/salesorder/9876", customrequest = "PATCH", postfields = params, httpheader = headers, followlocation = TRUE)
cat(res)
```
`DELETE` a document in the salesorder collection.
```r
library(RCurl)
headers = c(
"x-apikey" = "3c932310-3fab-4ba3-8102-b75ba0f05149"
)
res <- httpDELETE("https://mystore-fafb.api.codehooks.io/dev/salesorder/9876", httpheader = headers, followlocation = TRUE)
cat(res)
```
## Links for further reading
- [Database REST API](/docs/database-rest-api)
- [Database query language](/docs/nosql-database-query-language)
- [Serverless function NoSQL Database API](/docs/nosql-database-api)
---
## Concepts overview
*(Note: This content contains MDX/JSX code)*
Codehooks.io simplifies and speed up system integration and API development by combining serverless JavaScript functions and fast Database persistence with a coherent and well documented API. Applications are easily deployed to the secure and scalable Codehook managed cloud.
Forget about cluttering servers, databases, infrastrucure and protocols - just focus on what matters.
Codehooks includes these main features:
- **Studio** - Developer friendly web based tools for managing both data and code
- **Node.js and JavaScript** - Same language for frontend and backend development (ES6 and Typescript)
- **NPM** - Node.js package manager (npm) integration
- **Node.js Express** - API development with Express-like API
- **NoSQL** - Document database with Mongodb-like API
- **Key-Value** - Key-Value database with a Redis-like API (subset)
- **Worker Queues** - Persistent queues with worker functions
- **Background jobs** - Cron jobs with scheduled worker functions
- **CLI** - Powerful Command Line Interface (CLI) for productivity and DevOps/CI support
Read on to learn more about how development with Codehooks.io can simplify and add value to your project and team.
> Unless you've already have, please read the [quick start](/docs) first to learn how to install the CLI, and how to sign up/login to create your first Codehooks project.
## Codehooks main concepts
### Project
A project is the top level concept in Codehooks. A project is owned by an account (private or company) and contains one or multiple "spaces" with your deployed code, data and access credentials in an isolated and secure unit. You can create multiple projects within your account and you can join/invite others to a project. An example project structure is shown below:
- jane@example.com (private or corporate account owner)
- **customers** (project name)
- **dev** (development space)
- Database instance
- Security settings (API tokens, environment secrets, IP filters ...)
- Source code (deployed version 1.1.12)
- Developers (joe@example.com, pete@example.com)
- **prod** (production space)
- Database instance
- Security settings (API tokens, environment secrets, IP filters ...)
- Source code (deployed version 1.1.0)
- Developers (joe@example.com, pete@example.com)
- **salesdb** (project name for another project)
- ...
Projects are created with the CLI command `coho create` ([CLI docs here](cli#create))
:::info note
You can also manage your projects and spaces using the account user interface at [https://account.codehooks.io](https://account.codehooks.io).
:::
### The project application source code
A Codehooks application follows the familiar principles and best practices of modern JavaScript development. A project directory typically contains:
- An `index.js` file for the application main entry code
- A `package.json` file with library dependencies
- A `config.json` file with project information
We also recommend that you add source control (GIT) to manage your versions and branches.
Typically this will be a git repo with branches that are deployed to various spaces.
### A minimal example
The following example shows a complete process for creating a new project with an application that is deployed to the Codehooks serverless cloud.
First, create and setup a new project application:
```bash
coho create example
cd example
npm init --yes
npm install codehooks-js --save
```
After running the commands your project directory contains these files:
```bash
.
βββ config.json
βββ index.js
βββ node_modules
βββ package-lock.json
βββ package.json
```
If you inspect the source file index.js you'll see the default generated application code:
```js
/*
* Auto generated Codehooks (c) example
*/
import { app, crudlify } from 'codehooks-js';
// test route for https://.api.codehooks.io/dev/
app.get('/', (req, res) => {
res.send('CRUD server ready');
});
// Use Crudlify to create a REST API for any collection
crudlify(app);
// bind to serverless runtime
export default app.init();
```
We can now deploy the application (in our case example-4g9p) to the cloud with the CLI command `coho deploy` ([CLI docs here](cli#deploy)).
```bash
coho deploy
# Server output example
Project: example-4g9p Space: dev
Deployed Codehook successfully! π
```
After deployment we can inspect the application details with the `coho info` command, in this example showing our default space `dev` with its base endpoint URL and access token(s):
```bash
coho info --examples
Project name: example-4g9p
Team: YOUR-NAME (personal account)
API endpoint: https://example-4g9p.api.codehooks.io/dev/*
Spaces:
ββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ¬βββββββ¬βββββββ
β Name β Tokens β Jwks β Env β
ββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌβββββββΌβββββββ€
β dev (active) β a77926ca-xxx-yyyy-zzzzz-14eee564f8d5 (RW) β β β
ββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββ΄βββββββ΄βββββββ
```
After successful deployment we can test our application endpoint with curl. This minimal example and test shows that our application is deployed to a secure endpoint and returns the expected result:
```bash
curl -X GET 'https://example-4g9p.api.codehooks.io/dev' \
-H 'x-apikey: a77926ca-xxx-yyyy-zzzzz-14eee564f8d5'
# API output example from server
CRUD server ready
```
### Project spaces
A project space is a self-contained and isolated bundle of code, datastores and security settings within a specific project. All projects have a default space called `dev` which is created automatically when you add a project. You create new spaces with the `coho add` command ([CLI docs here](cli#add)). You can easily switch between different project spaces with the `coho use` command ([CLI docs here](cli#use)). Spaces can also have restricted access (team ADMINs only), which is convenient for production deployments.
For example, in your project you want to have isolated development, testing and production spaces.
```
Project-X
βββ dev
Β Β βββ source code (git branch dev)
Β Β βββ security settings & env variables
Β Β βββ data stores
βββ test
Β Β βββ source code (git branch test)
Β Β βββ security settings & env variables
Β Β βββ data stores
βββ prod (restricted)
Β Β βββ source code (git branch prod)
Β Β βββ security settings & env variables
Β Β βββ data stores
```
### Deploy your application to the Codehooks serverless cloud
Each project space is created as an isolated resource group in the Codehooks cloud.
For example, the `coho add prod` command creates a project space called `prod`. By switching to the space with the `coho use prod` command, this space is now the active (changed in the config.json project file).
On deployment with the `coho deploy` command, the current version of your application source code is packed and distributed as a serverless runtime to the **prod** space in the Codehook cloud.
Similarily, switching to another space, e.g. by using the `coho use dev`, sets the **dev** space as the active, and `coho deploy` will deploy the application to the active **dev** space.
This is a powerful feature, which effectively enables you to run multiple versions of your application with separate settings, environments and data stores.
### Built in Datastores
Persisting, querying and manipulating data is essential in any application. Codehooks comes with a built in datastore that supports **streaming** NoSQL and Key-Value operations. No need to think about drivers and protocols, it's built directly into the Codehooks APIs.
- NoSQL object data are accessed with the standard [Database API](nosql-database-api).
- Key-Value data are accessed with the standard [Key-Value API](key-value-database-api)
#### Data wrangling
Using the CLI command `coho import`, you can easily import any CSV or JSON file into a datastore.
Furthermore, the `coho query` command lets you query and transform data in advanced ways.
```bash title="Example: import and query of Stock data"
coho import -c stocks -f ~/Downloads/all_stocks_5yr.csv
coho query stocks --query 'Name=GOOG&open>10' --limit 2 --table
βββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ
β date β open β high β low β close β volume β Name β _id β
βββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββ€
β 2013-03-β¦ β 15.98 β 16.36 β 15.93 β 16.25 β 8383300 β GOOG β 18007b5fβ¦ β
βββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββ€
β 2013-03-β¦ β 14.7 β 14.93 β 14.5 β 14.82 β 9125300 β GOOG β 18007b5fβ¦ β
βββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ
```
### Fast development flow
The development process with Codehooks is fast.
You use your favorite tools (e.g. VS Code) combined with the CLI.
For example, after creating a new project and a project space, you write some code that you need to verify, test, debug and deploy. The following steps shows a typical development flow.
1. Open the index.js and other source files in your favorite code editor
2. Set the active space to deploy your application, e.g. `coho use dev`
3. Deploy the application with `coho deploy`
4. Check logs to see what's going on in your deployed application, `coho log --follow`
5. Find and fix bugs in your code, and redeploy with `coho deploy`
### Command Line Interface (CLI)
The Codehooks CLI is a key tool for managing your account, projects and applications. The CLI has all the commands and functionality you need to manage projects, spaces, security and your serverless JavaScript functions.
Codehooks has a complete set of features for teams to collaborate.
The [account UI](https://account.codehooks.io) lets you create teams and invite team members.
Read more about the CLI and the full set of commands and features [here](cli).
:::info note
You can also manage your projects and spaces using the account user interface at [https://account.codehooks.io](https://account.codehooks.io).
:::
---
## REST API query and Database CRUD API
*(Note: This content contains MDX/JSX code)*
A new Codehooks.io application has a complete and secure REST API for basic database CRUD (Create, Read, Update, Delete) operations using REST API queries. The CRUD REST API is implemented by bundling the deployed application with the NPM package [codehooks-js](https://www.npmjs.com/package/codehooks-js) and the [crudlify API](/docs/appeventapi#appcrudlifyschema-options).
When you create a new project a default auto generated application is deployed, this application implements a full database CRUD REST API with built-in NoSQL query functionality. The code for this is shown below.
```js
import { app } from 'codehooks-js';
app.crudlify(); // REST CRUD API
export default app.init();
```
All database URL endpoints are prefixed with the project ID, Codehooks API endpoint, and the database space name. For example, if your project ID is `myproject` and the database space is `dev`, then the full URL endpoint for the REST API is: `https://myproject-ff00.api.codehooks.io/dev/people?name=Ally`.
```mermaid
graph TD
A[REST API] --> B[CRUD Operations]
B --> C[Create=POST]
B --> D[Read/query=GET]
B --> E[Update=PUT/PATCH]
B --> F[Delete=DELETE]
C --> G[(Database)]
D --> G[(Database)]
E --> G[(Database)]
F --> G[(Database)]
accTitle: Database REST API query and CRUD
accDescr: You use the REST API to update the database using CRUD operations and REST API query
```
## Overview of Database REST API operations and queries
| API description | HTTP verb | Route |
| :------------------------------------------------------------------------ | --------- | --------------------------- |
| [Get list of documents](#get-list-of-documents) | `GET` | `/:collection` |
| [Get documents by query](#get-documents-by-query) | `GET` | `/:collection?query` |
| [Get document by ID](#get-document-by-id) | `GET` | `/:collection/:ID` |
| [Create a new document](#create-a-new-document) | `POST` | `/:collection` |
| [Update a document by ID](#update-a-document-by-id) | `PATCH` | `/:collection/:ID` |
| [Replace a document by ID](#replace-a-document-by-id) | `PUT` | `/:collection/:ID` |
| [Delete a document by ID](#delete-a-document-by-id) | `DELETE` | `/:collection/:ID` |
| [Update multiple documents by query](#update-multiple-documents-by-query) | `PATCH` | `/:collection/:ID/_byquery` |
| [Delete multiple documents by query](#delete-multiple-documents-by-query) | `DELETE` | `/:collection/:ID/_byquery` |
:::note Database collections
Create and manage collections with the Codehooks CLI command [`coho createcollection`](/docs/cli#createcollection) or using the Codehooks Studio [β Create collection] menu.
:::
:::note Security
All databases are protected with encrypted HTTPS protocol, secure API tokens or JWT tokens. You can also add IP filters and much more, read more about [Authentication here](/docs/authentication).
**API tokens**: List your database API tokens with the CLI command [`coho info`](/docs/cli.md#info). Create new API tokens with the CLI command [`coho add-token`](/docs/cli.md#add-token).
:::
## Database REST API
### Get list of documents
Retrieve all collection data.
**URL:** `/:collection[?options]`
**Method:** `GET`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
**Options:**
- **limit**: limit result, e.g. `?limit=2`
- **offset**: skip forward in data result set
- **fields**: comma separated list of fields to show, e.g. `?limit=2&fields=Last Name,Sex`
- **sort**: comma separated list of fields to sort result by, e.g. `?limit=2&sort=-Sex,Last Name`
**Returns** Array of JSON documents
**Code example**
```bash
curl 'https://myproject-ff00.api.codehooks.io/dev/people?limit=2' \
-H 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
**Success response** `200 OK`
```json
[
{
"Index": 54901,
"User Id": "d088FCEC6EDEF20",
"First Name": "Michaela",
"Last Name": "Callahan",
"Sex": "Male",
"Email": "yesenia31@example.net",
"Phone": "108-950-4850x6836",
"Date of birth": "2010-11-07",
"Job Title": "Research scientist (maths)",
"_id": "64bb88b51ed9a3057ac99d9c"
},
{
"Index": 54902,
"User Id": "40Fd72E5AaC5b67",
"First Name": "Lacey",
"Last Name": "Saunders",
"Sex": "Male",
"Email": "vcarey@example.net",
"Phone": "623.506.2528x932",
"Date of birth": "1946-06-28",
"Job Title": "Insurance claims handler",
"_id": "64bb88b51ed9a3057ac99d9d"
}
]
```
**Error response** `401 no access`
```html
401 no access
```
### REST API query - get documents by query
Retrieve collection data filtered by a query parameter.
:::tip
For a complete overview of the NoSQL query language and how to perform the REST API queries, check out the [NoSQL and REST API Query language](nosql-database-query-language) documentation.
:::
**URL:** `/:collection[?query&options]`
**Method:** `GET`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
**Query:**
- Simple REST API queries using URL parameters:
- **name=value** - e.g. `?First Name=Michaela`
- Advanced MongoDB query using URL JSON:
- **q=\{\.\.\.\}** - e.g. `?q={"First Name": {"$regex": "en"}, "Last Name": {"$in": ["Saunders", "Massey"]}}`
**Options:**
- **limit**: limit result, e.g. `?limit=2`
- **offset**: skip forward in data result set
- **fields**: comma separated list of fields to show, e.g. `?limit=2&fields=Last Name,Sex`
- **sort**: comma separated list of fields to sort result by, e.g. `?limit=2&sort=-Sex,Last Name`
**Returns** Array of JSON documents
**Code example:** simple query
```bash
curl --location 'https://myproject-ff00.api.codehooks.io/dev/people?First%20Name=Michaela&fields=First%20Name%2CEmail%2C_id' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
**Success response** `200 OK`
```json
[
{
"First Name": "Michaela",
"Email": "yesenia31@example.net",
"_id": "64bb88b51ed9a3057ac99d9c"
}
]
```
**Code example:** advanced query
:::tip URL encoding
Notice that advanced query parameter `q={...}` should be programmatically URL encoded before sent to the server.
E.g. the query:
```js
let query = {
"First Name": { "$regex": "en" },
"Last Name": { "$in": ["Saunders", "Massey"] }
}
```
For example, you can use JavaScript's `query = encodeURIComponent(JSON.stringify(query))`:
Then the encoded query equals:
```html
{%22First%20Name%22%3A{%22%24regex%22%3A%22en%22}%2C%22Last%20Name%22%3A{%22%24in%22%3A[%22Saunders%22%2C%22Massey%22]}}
```
:::
```bash
curl --location --globoff 'https://myproject-ff00.api.codehooks.io/dev/people?q={%22First%20Name%22%3A{%22%24regex%22%3A%22en%22}%2C%22Last%20Name%22%3A{%22%24in%22%3A[%22Saunders%22%2C%22Massey%22]}}' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
**Success response** `200 OK`
```json
[
{
"Index": 54907,
"User Id": "18E57F5a6aaC1ab",
"First Name": "Darlene",
"Last Name": "Saunders",
"Sex": "Female",
"Email": "noah55@example.net",
"Phone": "001-158-700-3226",
"Date of birth": "1970-08-23",
"Job Title": "Scientist, forensic",
"_id": "64bb88b51ed9a3057ac99da2"
},
{
"Index": 54933,
"User Id": "aeD915eA429Fb02",
"First Name": "Lauren",
"Last Name": "Massey",
"Sex": "Male",
"Email": "stuart55@example.net",
"Phone": "+1-716-581-5746x2442",
"Date of birth": "2001-10-21",
"Job Title": "Information systems manager",
"_id": "64bb88b51ed9a3057ac99dbc"
}
]
```
**Error response** `401 no access`
```html
401 no access
```
**Error response** `400 Bad Request`
```html
Unexpected token } in JSON at position 5
```
### Get document by ID
Retrieve a document from a collection.
**URL:** `/:collection/:ID`
**Method:** `GET`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
- **ID**: an existing document `_id` value
**Returns** A JSON document
**Code example**
```bash
curl --location 'https://myproject-ff00.api.codehooks.io/dev/people/64bb88b51ed9a3057ac99d9c' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
**Success response** `200 OK`
```json
{
"Index": 54901,
"User Id": "d088FCEC6EDEF20",
"First Name": "Michaela",
"Last Name": "Callahan",
"Sex": "Male",
"Email": "yesenia31@example.net",
"Phone": "108-950-4850x6836",
"Date of birth": "2010-11-07",
"Job Title": "Research scientist (maths)",
"_id": "64bb88b51ed9a3057ac99d9c"
}
```
**Error response** `401 no access`
```
401 no access
```
**Error response** `404 Not Found`
```
3 INVALID_ARGUMENT: NotFoundError: Key not found in database [64bb88b51ed9a3057ac99d9cc]
```
### Create a new document
Insert a new JSON document in a collection.
**URL:** `/:collection`
**Method:** `POST`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
- **body**: a valid JSON document
**Returns** The created JSON document
**Code example**
```bash
curl --location 'https://myproject-ff00.api.codehooks.io/dev/people' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149' \
--header 'Content-Type: application/json' \
--data-raw '{
"First Name": "Jim",
"Last Name": "Callahan",
"Email": "jim@example.net",
"Phone": "108-950-4850x6836",
"Date of birth": "1995-11-07",
"Job Title": "Software tester"
}'
```
**Success response** `201 Created`
```js
{
"First Name": "Jim",
"Last Name": "Callahan",
"Email": "jim@example.net",
"Phone": "108-950-4850x6836",
"Date of birth": "1995-11-07",
"Job Title": "Software tester",
"_id": "64bbc861f13609074a5d981a"
}
```
**Error response** `401 no access`
```
401 no access
```
**Error response** `400 Bad Request`
E.g. error when POST an invalid JSON document or a JSON-schema `schemaError` error occurs.
```
{
"schemaError": [
{
"instancePath": "",
"schemaPath": "#/required",
"keyword": "required",
"params": {
"missingProperty": "firstName"
},
"message": "must have required property 'firstName'"
}
]
}
```
### Update a document by ID
Update a JSON document in a collection.
**URL:** `/:collection/:ID`
**Method:** `PATCH`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
- **ID**: a valid document `_id`
- **body**: a valid JSON document
**Returns** The updated JSON document
**Code example**
```bash
curl --location --request PATCH 'https://myproject-ff00.api.codehooks.io/dev/people/64bbc861f13609074a5d981a' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149' \
--header 'Content-Type: application/json' \
--data '{
"Phone": "123-345-12332x6836",
"Job Title": "Bug master"
}'
```
**Success response** `200 OK`
```js
{
"First Name": "Jim",
"Last Name": "Callahan",
"Email": "jim@example.net",
"Phone": "123-345-12332x6836",
"Date of birth": "1995-11-07",
"Job Title": "Bug master",
"_id": "64bbc861f13609074a5d981a"
}
```
**Error response** `401 no access`
```
401 no access
```
**Error response** `400 Bad Request`
E.g. invalid JSON document or `schemaError`.
```
{
"schemaError": [
...
]
}
```
### Replace a document by ID
Replace a JSON document in a collection.
**URL:** `/:collection/:ID`
**Method:** `PUT`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
- **ID**: a valid document `_id`
- **body**: a valid JSON document
**Returns** The replaced JSON document
**Code example**
```bash
curl --location --request PUT 'https://myproject-ff00.api.codehooks.io/dev/people/64bbc861f13609074a5d981a' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149' \
--header 'Content-Type: application/json' \
--data '{
"First Name": "Re",
"Last Name": "Placed",
"Job Title": "Replacer"
}'
```
**Success response** `200 OK`
```json
{
"First Name": "Re",
"Last Name": "Placed",
"Job Title": "Replacer",
"_id": "64bbc861f13609074a5d981a"
}
```
**Error response** `401 no access`
```
401 no access
```
E.g. invalid JSON document or `schemaError`.
```
{
"schemaError": [
...
]
}
```
### Delete a document by ID
Delete a JSON document in a collection.
**URL:** `/:collection/:ID`
**Method:** `DELETE`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
- **ID**: a valid document `_id`
**Returns** The deleted document \_id
**Code example**
```bash
curl --location --request DELETE 'https://myproject-ff00.api.codehooks.io/dev/people/64bbc861f13609074a5d981a' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
**Success response** `200 OK`
```json
{
"_id": "64bbc861f13609074a5d981a"
}
```
**Error response** `401 no access`
```
401 no access
```
### Update multiple documents by query
Update multiple JSON document by query in a collection.
**URL:** `/:collection/_byquery[?query&options]`
**Method:** `PATCH`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
- **body**: a valid JSON document to update on all matches
**Query:**
- Simple REST API queries using URL parameters:
- **name=value** - e.g. `?First Name=Michaela`
- Advanced MongoDB query using URL JSON:
- **q=\{\.\.\.\}** - e.g. `?q={"First Name": {"$regex": "en"}, "Last Name": {"$in": ["Saunders", "Massey"]}}`
**Returns** Count of updated documents
**Code example**
```bash
curl --location --request PATCH 'https://myproject-ff00.api.codehooks.io/dev/people/_byquery?Job%20Title=%22Scientist%2C%20forensic%22' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149' \
--header 'Content-Type: application/json' \
--data '{
"$set": {
"salary": "high"
},
"$inc": {
"visited": 1
}
}'
```
**Success response** `200 OK`
```json
{
"count": 2
}
```
**Error response** `401 no access`
```
401 no access
```
**Error response** `400 Bad Request`
E.g. invalid JSON document or `schemaError`.
```
{
"schemaError": [
...
]
}
```
### Delete multiple documents by query
Delete multiple JSON document by query in a collection.
:::warning Erases all matched documents by query
Please ensure that the applied query is correct. An empty query parameter will erase all documents in collection.
:::
**URL:** `/:collection/_byquery[?query&options]`
**Method:** `DELETE`
**Parameters:**
- **collection**: a valid collection name, e.g. `people`
**Query:**
- Simple REST API queries using URL parameters:
- **name=value** - e.g. `?First Name=Michaela`
- Advanced MongoDB query using URL JSON:
- **q=\{\.\.\.\}** - e.g. `?q={"First Name": {"$regex": "en"}, "Last Name": {"$in": ["Saunders", "Massey"]}}`
**Returns** Count of deleted documents
**Code example**
```bash
curl --location --request DELETE 'http://myproject-ff00.api.codehooks.io/dev/people/_byquery?Job%20Title=%22Scientist%2C%20forensic%22' \
--header 'x-apikey: 3c932310-3fab-4ba3-8102-b75ba0f05149'
```
**Success response** `200 OK`
```json
{
"count": 2
}
```
**Error response** `401 no access`
```
401 no access
```
## Data Schema support
We recommend to add JSON-schema directly to the database collection using the [CLI command](/docs/cli#add-schema) `add-schema` or the Codehooks Studio application navigating to Collection/Settings/JSON-Schema.

However, you can also take full control over the data validation in your code using these popular technologies described in the following sections.
Codehooks supports these popular data and validation schemas:
- [Yup](https://www.npmjs.com/package/yup) - Dead simple Object schema validation
- [Zod](https://www.npmjs.com/package/zod) - TypeScript-first schema validation with static type inference
- [JSON.schema](https://www.npmjs.com/package/z-schema) - Standard declarative language that allows you to annotate and validate JSON documents.
### Data validation with Yup
The code example below shows how easy it is to validate data with a [Yup](https://www.npmjs.com/package/yup) data schema.
Run `npm i yup` in project folder.
```js
import { app } from 'codehooks-js';
import { object, string, number, date } from 'yup';
// database schema
const userSchema = object({
name: string().required(),
age: number().required().positive().integer(),
email: string().email(),
website: string().url().nullable(),
createdOn: date().default(() => new Date()),
});
app.crudlify({ user: userSchema });
export default app.init(); // export app to a runtime server engine
```
### Data validation with JSON-schema
The next code example uses JSON-schema to validate data.
Run `npm i z-schema` in project folder.
```js
import { app } from 'codehooks-js';
import ZSchema from 'z-schema';
const userSchema = {
id: 'personDetails',
type: 'object',
properties: {
firstName: { type: 'string' },
lastName: { type: 'string' },
},
required: ['firstName', 'lastName'],
};
app.crudlify({ user: userSchema });
// bind to serverless runtime
export default app.init();
```
### Data validation with Zod
Zod is another popular data validation library.
Run `npm i zod` in project folder.
A simple code example using Zod is show below.
```js
import { app } from 'codehooks-js'; // Standard Codehooks.io lib
import { z } from 'zod';
const userSchema = z
.object({
username: z.string(),
email: z.string().email(),
status: z.boolean().default(true),
})
.required({ username: true, email: true });
app.crudlify({ user: userSchema });
export default app.init(); // Bind functions to the serverless cloud
```
## Database event hooks middleware
To provide additional CRUD logic, events are triggered before and after a database operation.
```js
hooks.before(, handlerFunction)
hooks.after(, handlerFunction)
```
Example event hooks are shown in the code example below.
```js
... // collapsed code
app.crudlify({user: userSchemaYup}, options).then((hooks) => {
hooks.beforePOST('user', async (data) => {
console.log("User data before saving", data)
// abort operation with a throw, cases 404 status code
// E.g. throw new Error(`BAD post for ${data}`)
// mutate data before saved to the database
data.foo = 'Was here!'
})
hooks.afterPOST('user', async (data) => {
console.log("User data after saved to the database", data)
})
})
... // collapsed code
```
## Examples
### Insert a new `user` to the database
POST a new user using curl.
```bash
curl -X POST \
'https://myproject-fef0.api.codehooks.io/dev/user' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Ally",
"email": "ally@example.com"
}'
```
### Validate data against a Yup data schema
Check that the data schema validates correctly by sending an invalid email address.
```bash
curl -X POST \
'https://myproject-fef0.api.codehooks.io/dev/user' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Sally",
"email": "sally.example.com"
}'
```
Validation error shows that Yup works.
```bash
400 Bad Request
{
"value": {
"active": true,
"email": "sally.example.com",
"name": "Sally"
},
"path": "email",
"type": "email",
"errors": [
"email must be a valid email"
],
... # chopped error message
}
```
### Run a database REST API query
```bash
curl -X GET \
'https://myproject-fef0.api.codehooks.io/dev/user?name=Ally' \
--header 'Content-Type: application/json'
```
Example output.
```json
[
{
"_id": "63fb97825f624f479034eb08",
"active": true,
"email": "ally@example.com",
"name": "Ally"
}
]
```
### Update a record in the database
```bash
curl -X PATCH \
'https://myproject-fef0.api.codehooks.io/dev/user/63fb97825f624f479034eb08' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Ally Mc. Beal"
}'
```
### Querying the database using the REST API
You can query the database REST API in two different ways.
**Simple** to use and quick queries with the URL:
```url
https://myproject-fef0.api.codehooks.io/dev/user?name=jane&age>23&limit=2&offset=5&sort=email&fields=name,age
```
Which actually produces this query object:
```js
{ name: 'Jane', age: { '$gt': 23 } }, {
fields: { name: 1, age: 1 },
sort: { email: 1 },
skip: 5,
limit: 2,
projection: { name: 1, age: 1 }
}
```
For **advanced** use, and programmatic approach, pass inn the full NoSQL/JSON REST API query and hints as URL parameters:
```url
https://myproject-fef0.api.codehooks.io/dev/user?q={"name": "Jane", "age": {"$gt": 23}}&h={"fields": { "name": 1, "age": 1 },"sort": {"email": 1 },"skip": 5,"limit": 2,"projection": { "name": 1, "age": 1 }}
```
The last option would probably use `JSON.stringify(query)` etc. to produce a valid query in the URL.
---
## Data operators
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
Data objects in a datastore can be manipulated in any update operation. To manipulate one or many data object(s) a range of special operators can be applied using the [`updateOne(...)`](nosql-database-api#updateonecollection-id-document) or [`updateMany(...)`](nosql-database-api#updatemanycollection-document-options) API.
**Operators quick overview**
## **$inc** operator
Increase or decrease a field value on an existing datastore object.
**Syntax**: `{ $inc: { : , : , ... } }`
Example:
```js
// before
{..., "followers": 42}
const operator = {$inc: {"followers": 1}}
// Call update API, e.g. updateMany or updateOne
await conn.updateMany('mycollection', {"status": "ACTIVE"}, operator)
// after
{..., "followers": 43}
```
π _Use negative values to decrement field._
## **$push** operator
Append an element to a sub array on an existing datastore object.
**Syntax**: `{ $push: { : , ... } }`
Example:
```js
// before
{"followers": 42, "comments": ["one", "three"]}
const operator = {$push: {"comments": "two"}}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{"followers": 42, "comments": ["one", "three", "two"]}
```
## **$pull** operator
Remove an element from a sub array on an existing datastore object.
Pulls all matches from array.
**Syntax**: `{ $pull: { : |, : |, ... } }`
Example:
```js
// before
{"followers": 42, "comments": ["one", "two", "three"]}
const operator = {$pull: {"comments": "two"}}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{"followers": 42, "comments": ["one", "three"]}
```
## **$pop** operator
Removes last element of a sub array on an existing datastore object.
**Syntax**: `{ $pop: { : -1 | 1, ... } }`
-1 pop first item of array, 1 pop last item of array.
Example:
```js
// before
{"followers": 42, "comments": ["one", "three", "two"]}
const operator = {$pop: {"comments": 1}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{"followers": 42, "comments": ["one", "three"]}
```
## **$addToSet** operator
Insert an unique element to a sub array on an existing datastore object.
Nothing is inserted if item already exists in set.
**Syntax**: `{ $addToSet: { : , ... } }`
Example:
```js
// before
{"followers": 42, "comments": ["one", "two", "three"]}
const operator = {$addToSet: {"items": {"name": "one", "price": 1}}}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{
"followers": 42, "comments": ["one", "two", "three"],
"items": [{"name": "one", "price": 1}]
}
```
## **$set** operator
Set any object value.
**Syntax**: `{ $set: { : , ... } }`
Example:
```js
// before
{"followers": 42, "comments": ["one", "two", "three"]}
const operator = {$set: {"address": {"street": "Main street", "ZIP": 1123}}}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{
"followers": 42, "comments": ["one", "two", "three"],
"address": {"street": "Main street", "ZIP": 1123}
}
```
## **$unset** operator
Delete an object property.
**Syntax**: `{ $unset: { : "", ... } }`
```js
// before
{"followers": 42, "comments": ["one", "two", "three"]}
const operator = {$unset: {"comments": ""}}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{
"followers": 42
}
```
## **$rename** operator
Rename an object property.
**Syntax**: `{$rename: { : , : , ... } }`
```js
// before
{"followers": 42, "comments": ["one", "two", "three"]}
const operator = {$rename: {"comments": "claps"}}
// E.g.: await conn.updateOne('customer', ID, operator);
// after
{
"followers": 42, "claps": ["one", "two", "three"]
}
```
---
## Alpine.js tutorial
Moved to: [/blog/connecting-alpine-js-to-database-rest-api-guide](/blog/connecting-alpine-js-to-database-rest-api-guide)
---
## AWS S3 integration
*(Note: This content contains MDX/JSX code)*

In this example we'll create an API to upload and download binary files to AWS S3 (v3).
You can read more in this blog post about [AWS S3 integration](https://dev.to/restdbjones/step-by-step-guide-uploading-and-downloading-binary-files-to-aws-s3-v3-using-nodejs-and-codehooksio-4olh)
Also check out the full [example source code here](https://github.com/RestDB/codehooks-io-examples/tree/main/aws-s3).
## Initialise the project
Create a new project and install all packages.
```bash
coho create mys3project
cd mys3project
npm init es6 -y
npm install codehooks-js
npm install @aws-sdk/client-s3
```
## Add credentials and secrets
Use the CLI or the Admin UI to add your secret environment variables. Note the `--encrypted` flag to prevent reading the actual data content of the variables.
```bash
coho set-env AWS_ACCESS_KEY_ID 'YOUR_KEY' --encrypted
coho set-env AWS_SECRET_ACCESS_KEY 'YOUR_SECRET' --encrypted
coho set-env AWS_BUCKET 'YOUR_BUCKET' --encrypted
coho set-env AWS_REGION 'YOUR_REGION' --encrypted
```
## Example source code
The example app shown below creates an API for upload and download of binary files to and from AWS S3.
```js
/*
* Codehooks (c) AWS S3 example
*/
import { app } from 'codehooks-js'
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'
import { PassThrough } from 'stream'
const { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_BUCKET } = process.env;
const s3config = {
region: AWS_REGION,
AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY
}
const s3client = new S3Client(s3config);
// public access
app.auth('/download/*', (req, res, next) => {
if (req.method === 'GET') {
next()
} else {
res.status(403).end('Not public')
}
})
// API to GET a binary data stream from AWS S3
app.get('/download/:file', async (req, res) => {
try {
// filename from route
const { file } = req.params;
const input = {
"Bucket": AWS_BUCKET,
"Key": `tmp/${decodeURI(file)}` // decode filename and get from /bucket/tmp/file
};
// Create get command
const command = new GetObjectCommand(input);
// Send get command
const response = await s3client.send(command);
// set content-type
res.set('content-type', response.ContentType)
// stream data back to client
response.Body.pipe(res.writable)
} catch (error) {
// Woops
res.status(400).end(error.message)
}
})
// API to POST a binary data stream to AWS S3
app.post('/upload/single', async (req, res) => {
try {
// get size, type and filename from destructured header values
const { 'content-length': ContentLength, 'content-type': ContentType, filename } = req.headers;
const input = {
"Bucket": AWS_BUCKET,
"Key": `tmp/${filename}`, // emulate file system /bucketname/tmp/filename
"Body": new PassThrough(), // stream to pipe data through
"ContentLength": ContentLength,
"ContentType": ContentType
};
// pipe binary request data to S3 stream
req.pipe(input.Body);
// create put command
const command = new PutObjectCommand(input);
// execute put object command
const response = await s3client.send(command);
// return data to client
res.json(response);
} catch (error) {
// some error occured, return 400 status to client
res.status(400).end(error.message)
}
})
// bind to serverless runtime
export default app.init();
```
## Testing the API
Let's test the API with curl by uploading a test file from a local filesystem. Use `coho info --examples` to inspect your project API endpoint.
**Upload a file**
```bash
curl --location 'https://.api.codehooks.io/dev/upload/single' \
--header 'x-apikey: XXXXX' \
--header 'filename: myfile.png' \
--header 'Content-Type: image/png' \
--header 'x-apikey: YOUR_API_TOKEN' \
--data '@./myfile.png'
```
**Download a file**
```bash
curl --location 'https://.api.codehooks.io/dev/download/myfile.png' \
--output myfile.png
```
---
## ChatGPT REST API node.js
*(Note: This content contains MDX/JSX code)*

This is an example of a serverless Node.js application that uses codehooks.io to create a REST API for the OpenAI [ChatGPT](https://chat.openai.com) language model. The API allows REST API clients to send text prompts to the ChatGPT model and receive a response in return.
The code uses the [codehooks-js](https://www.npmjs.com/package/codehooks-js) library to handle routing and middleware for the API, and the [node-fetch](https://www.npmjs.com/package/node-fetch) library to make HTTP requests to the OpenAI API. The code also uses the built-in codehooks.io Key-Value store to cache the response from the OpenAI API for 60 seconds, in order to reduce the number of requests made to the OpenAI API (If you want to create your own Node.js Express backend, you just need to replace this cache with [Redis](https://redis.io/) for example).
Additionally, the code also implements a middleware to rate limit the number of requests coming from the same IP address to 10/min.
:::info Documentation
- Read more about the [OpenAI API](https://platform.openai.com/docs/api-reference/introduction)
- Read more about the [Key-Value store ](/docs/key-value-database-api)
:::
## Create the OpenAI API key
Navigate to your [Open AI account settings](https://platform.openai.com/settings/organization/api-keys) and create a new secret key.

## Create a new project to host the GPT REST API
```bash
coho create gptrestapi
```
This will create a new directory for your REST API backend application.
```bash
cd gptrestapi
```
## Copy the source code
```js title="index.js"
import { app, Datastore } from 'codehooks-js';
import fetch from 'node-fetch';
// REST API routes
app.post('/chat', async (req, res) => {
if (!process.env.OPENAI_API_KEY) {
return res.status(500).json({ error: 'Missing OPENAI_API_KEY' }); // CLI command: coho set-env OPENAI_API_KEY 'XXX'
}
const { ask } = req.body;
if (!ask) {
return res.status(400).json({ error: 'Missing ask parameter' });
}
const db = await Datastore.open();
const cacheKey = `chatgpt_cache_${ask}`;
// Check cache first
const cachedAnswer = await db.get(cacheKey);
if (cachedAnswer) {
return res.json({ response: cachedAnswer });
}
try {
// Call OpenAI API
const response = await callOpenAiApi(ask);
const { choices } = response;
const text = choices?.[0]?.message?.content || 'No response';
// Cache response for 1 minute
await db.set(cacheKey, text, { ttl: 60 * 1000 });
res.json({ response: text });
} catch (error) {
console.error('OpenAI API error:', error);
res.status(500).json({ error: 'Failed to get response from OpenAI' });
}
});
// Call OpenAI API
async function callOpenAiApi(ask) {
const requestBody = {
model: 'gpt-4-turbo',
messages: [
{ role: 'system', content: 'You are a helpful AI assistant.' },
{ role: 'user', content: ask },
],
temperature: 0.6,
max_tokens: 1024,
};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify(requestBody),
};
const response = await fetch(
'https://api.openai.com/v1/chat/completions',
requestOptions
);
return response.json();
}
// Global middleware to IP rate limit traffic
app.use(async (req, res, next) => {
const db = await Datastore.open();
const ipAddress = req.headers['x-real-ip'] || req.ip;
// Increase count for IP
const count = await db.incr(`IP_count_${ipAddress}`, 1, { ttl: 60 * 1000 });
console.log('Rate limit:', ipAddress, count);
if (count > 10) {
return res.status(429).json({ error: 'Too many requests from this IP' });
}
next();
});
// Export app to the serverless runtime
export default app.init();
```
Open the `index.js` file and replace the code with the source code.
## Install dependencies
```bash
npm install codehooks-js node-fetch
```
## Create a new secret environment variable
Once you have created your Open AI API secret key, you need to add this to the serverless runtime. You can use the [CLI command](/docs/cli.md#set-env) for this.
```bash
coho set-env OPENAI_API_KEY 'XXXXXXXX' --encrypted
```
## Deploy the Node.js ChatGPT REST API to the codehooks.io backend
From the project directory run the [CLI command](/docs/cli#deploy) `coho deploy`.
The output from my test project is shown below:
```bash
coho deploy
Project: gptrestapi-pwnj Space: dev
Deployed Codehook successfully! π
```
## Test your ChatGPT REST API
The following curl example demonstrates that we have a live REST API that takes a `POST` body with a text string `ask` and returns the response from the Open AI API.
```bash
curl -X POST \
'https://gptrestapi-pwnj.api.codehooks.io/dev/chat' \
--header 'x-apikey: cb17c77f-2821-489f-a6b4-fb0dae34251b' \
--header 'Content-Type: application/json' \
--data-raw '{
"ask": "Make a great title for a code example with ChatGPT and serverless node.js with codehooks.io"
}'
```
Output from the ChatGPT REST API is shown below.
```bash
"Building Conversational AI with ChatGPT and Serverless Node.js Using Codehooks.io"
```
Another example shows that we a actually talking to the Open AI API.
```bash
curl -X POST \
'https://gptrestapi-pwnj.api.codehooks.io/dev/chat' \
--header 'x-apikey: cb17c77f-2821-489f-a6b4-fb0dae34251b' \
--header 'Content-Type: application/json' \
--data-raw '{
"ask": "Tell a joke"
}'
```
Output from the second example π:
```bash
Q: What did the fish say when it hit the wall?
A: Dam!
```
The source code of this and other examples are also available on our [GitHub example repo](https://github.com/RestDB/codehooks-io-examples/tree/main/chatgpt-rest-api).
---
## CRUD REST API Example with Node.js and codehooks.io | Step-by-Step Tutorial
*(Note: This content contains MDX/JSX code)*
# CRUD REST API Example - how to build a complete CRUD REST API with Node.js and codehooks.io
This comprehensive tutorial shows you how to build a complete CRUD (Create, Read, Update, Delete) REST API from scratch. You'll learn how to implement all essential database operations through a RESTful interface, with working example code that you can run immediately.
This CRUD REST API example demonstrates:
- Full database CRUD operations (Create, Read, Update, Delete)
- RESTful endpoint implementation
- Query parameter handling
- NoSQL database integration
- Error handling
- Production-ready code structure
The CRUD REST API example lets clients access your data, e.g.
- Search database with url query strings, e.g. `GET` `/dev/customer?name=Bill&city=Berlin`
- Get object by ID, e.g. `GET` `/dev/customer/17f7990aa6d-w84jerq77u7w3d`
- Create new objects from JSON data, e.g. `POST` `/dev/customer` `{JSON}`
:::tip
**Automate** the creating of a CRUD REST API with the open source library [Crudlify](https://www.npmjs.com/package/codehooks-crudlify).
Read more about how to about how to automate the development and deployment of CRUD REST API's in [this blog post](/blog/how-to-quickly-create-a-crud-rest-api-backend-using-codehooks-io-with-code-example).
:::
## Project Structure for the CRUD REST API Example
The project has one main JavaScript file ([index.js](#main-function-indexjs)) and four JavaScript library files: [create.js](#create-function), [read.js](#read-function), [update.js](#update-function) and [delete.js](#delete-function).
1. Create a project with the `coho create` CLI command.
2. Change to the project directory and install `npm i query-to-mongo codehooks-js -s`.
3. Delete default index.js file and create new files `touch index.js create.js read.js delete.js update.js`
4. Open code files in your favorite code editor and enter the follwing code.
## CRUD REST API Example: Main function (index.js)
The index.js file defines the HTTP routes (routehooks) for the serverless functions. The functions are implemented as separate code files for clarity and better organisation of the project.
```js title=index.js
import app from 'codehooks-js';
// import libraries from separate files
import { createFunc } from './create';
import { readOneFunc, readManyFunc } from './read';
import { deleteFunc } from './delete';
import { updateFunc } from './update';
// Codehooks API routes for the CRUD REST API example
app.post('/:collection', createFunc);
app.get('/:collection', readManyFunc);
app.get('/:collection/:ID', readOneFunc);
app.put('/:collection/:ID', updateFunc);
app.delete('/:collection/:ID', deleteFunc);
export default app.init();
```
## Create function (create.js)
```js title=create.js
import { Datastore } from 'codehooks-js';
export async function createFunc(req, res) {
const { collection } = req.params;
const document = req.body;
const conn = await Datastore.open();
const result = await conn.insertOne(collection, document);
res.json(result);
}
```
## Read function (read.js)
```js title=read.js
// use npm package to convert URL query string to MongoDB query filter
const q2m = require('query-to-mongo');
import { Datastore } from 'codehooks-js';
export async function readManyFunc(req, res) {
const { collection } = req.params;
const conn = await Datastore.open();
const options = {
filter: q2m(req.query).criteria,
};
conn.getMany(collection, options).json(res);
}
export async function readOneFunc(req, res) {
const { collection, ID } = req.params;
const conn = await Datastore.open();
try {
const result = await conn.getOne(collection, ID);
res.json(result);
} catch (e) {
res
.status(404) // not found
.end(e);
}
}
```
## Update function (update.js)
```js title=update.js
import { Datastore } from 'codehooks-js';
export async function updateFunc(req, res) {
const { collection, ID } = req.params;
const document = req.body;
const conn = await Datastore.open();
const result = await conn.updateOne(collection, ID, document, {});
res.json(result);
}
```
## Delete function (delete.js)
```js title=delete.js
import { Datastore } from 'codehooks-js';
export async function deleteFunc(req, res) {
const { collection, ID } = req.params;
const conn = await Datastore.open();
const result = await conn.removeOne(collection, ID, {});
res.json(result);
}
```
## Deploying the CRUD REST API Example
You can deploy the code with this [CLI command](/docs/cli#deploy):
```bash
coho deploy
```
## Testing the deployed CRUD REST API Example
Test your deployed CRUD REST API with curl or [Postman](https://getpostman.com) (recommended).
:::tip
Use the CLI command `coho info` to see API tokens for the project datastore.
:::
### POST
```bash title="Shell command"
curl --location --request POST 'https://.api.codehooks.io/dev/customer' \
--header 'x-apikey: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Bill",
"email": "william@example.com"
}'
```
```bash title="Example output"
{"name":"Billy Bob","email":"william@example.com","_id":"640f60e2b4fdc00015c4280f"
```
### PUT
```bash title="Shell command"
curl --location --request PUT 'https://.api.codehooks.io/dev/customer/640f60e2b4fdc00015c4280f' \
--header 'x-apikey: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Billybob"
}'
```
```bash title="Example output"
{
"name": "Billybob",
"email": "william@example.com",
"_id": "640f60e2b4fdc00015c4280f"
}
```
### GET (all)
```bash title="Shell command"
curl --location --request GET 'https://.api.codehooks.io/dev/customer' \
--header 'x-apikey: '
```
```bash title="Example putput"
{
"name": "Billybob",
"email": "william@example.com",
"_id": "640f60e2b4fdc00015c4280f"
}
...
```
### GET (by query)
```bash title="Shell command"
curl --location --request GET 'https://.api.codehooks.io/dev/customer?email=william@example.com' \
--header 'x-apikey: '
```
```bash title="Example putput"
{
"name": "Billybob",
"email": "william@example.com",
"_id": "640f60e2b4fdc00015c4280f"
}
```
### DELETE
```bash title="Shell command"
curl --location --request DELETE 'https://.api.codehooks.io/dev/customer/640f60e2b4fdc00015c4280f' \
--header 'x-apikey: '
```
```bash title="Example putput"
{
"_id": "640f60e2b4fdc00015c4280f"
}
```
---
## Data import
*(Note: This content contains MDX/JSX code)*
Codehooks supports import of large data sets from CSV and JSON files.
In this example we'll use a dataset from Kaggle machine learning data site.
The data contains 5 years of stock trading results with 619040 records.
You'll find the dataset here https://www.kaggle.com/datasets/camnugent/sandp500
After downloading the data we use the CLI to import the data file to a collection `stocks`.
```bash title="CLI command"
coho import -c stocks -f ~/Downloads/all_stocks_5yr.csv
Codehooks data import [==============================] 178673/bps 100% 0.0s
Finished import of 619040 objects
```
We can inspect that the dataset is imported correctly with the `coho query` command.
```bash title="CLI command"
coho query stocks --limit 2
```
```bash title="output"
{
date: '2013-03-14',
open: 15.98,
high: 16.36,
low: 15.93,
close: 16.25,
volume: 8383300,
Name: 'AAL',
_id: '18007b5f8d7-000mnf5l50qd7a'
}
{
date: '2013-03-07',
open: 14.7,
high: 14.93,
low: 14.5,
close: 14.82,
volume: 9125300,
Name: 'AAL',
_id: '18007b5f8d7-058mo6lu276dq0'
}
```
We can also perform complex queries on datasets.
E.g. find all Google trades that closed above $110.
```bash title="CLI command"
coho query stocks 'Name=GOOG&close>1100'
```
```bash title="output"
{
date: '2018-01-23',
open: 1159.85,
high: 1171.63,
low: 1158.75,
close: 1169.97,
volume: 1333056,
Name: 'GOOG',
_id: '640f60e2b4fdc00015c4280f'
}
{
date: '2018-01-31',
open: 1170.57,
high: 1173,
low: 1159.13,
close: 1169.94,
volume: 1538688,
Name: 'GOOG',
_id: '640f60f2b4fdc00015c42811'
}
...
```
:::tip
Use the `coho query` `--table` option to display data in tabular form. Use the `--raw` option to output data for further processing.
:::
```bash title="CLI command"
coho query stocks --limit 2 --table
βββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ
β date β open β high β low β close β volume β Name β _id β
βββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββ€
β 2013-03-β¦ β 15.98 β 16.36 β 15.93 β 16.25 β 8383300 β AAL β 18007b5fβ¦ β
βββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββ€
β 2013-03-β¦ β 14.7 β 14.93 β 14.5 β 14.82 β 9125300 β AAL β 18007b5fβ¦ β
βββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ
```
---
## GitHub example code

Navigate to the [GitHub examples](https://github.com/RestDB/codehooks-io-examples) repository to learn more from fully working examples and how-to guides. The repository contains example code for various topics [from our blog](/blog), including the following:
* Mailgun integration
* React
* ChatGpt
* mongoDB and Express
* CRUD
Get started by cloning the repo to your local machine.
```bash
git clone https://github.com/RestDB/codehooks-io-examples.git
```
---
## GraphQL API with database
*(Note: This content contains MDX/JSX code)*
This serverless Codehooks example backend exposes a graphql endpoint for CRUD operations.
* You can use [Postman](https://postman.com) to test the API. Postman automatically fetches the GraphQL schema and gives you auto-completion π€
* Data is persisted in the NoSQL document database.
:::tip
Visit [the official GraphQL site](https://www.graphql.com/) to learn more about GraphQL.
:::
1. Create a project with the `coho create` CLI command.
2. Change to the project directory and install using `npm i codehooks-js graphql graphql-tools`.
3. Open the index.js file in your favorite code editor and enter the following code.
## Main file (index.js)
The index.js file defines the schema, the resolvers and the single GraphQL route we need.
```js title=index.js
import { app, Datastore } from 'codehooks-js';
import { graphql } from 'graphql';
import { makeExecutableSchema } from 'graphql-tools';
// Construct a schema, using GraphQL schema language
const typeDefs = `
type Member {
id: ID
name: String!
age: Int
active: Boolean
}
type Query {
ping: String
member(id: ID!): Member!
allMembers: [Member]
}
type Mutation {
addMember(name: String! age:Int active:Boolean): Member!
updateMember(id: ID! name: String age:Int active:Boolean): Member!
deleteMember(id: ID!): ID
}
`;
// implement resolvers for queries and mutations
const resolvers = {
Query: {
ping: () => {
return 'Pong!';
},
member: async (obj, { id }, { db } ) => {
const { _id, ...rest } = await db.getOne('members', id);
return { id: _id, ...rest };
},
allMembers: async (obj, args, { db }) => {
const members = await db.getArray('members');
return members.map(({_id, ...member }) => ({id: _id, ...member })); // map _id to id
}
},
Mutation: {
addMember: async (obj, input, { db }) => {
const {_id, ...rest } = await db.insertOne('members', input);
return { id: _id, ...rest };
},
updateMember: async(obj, {id, ...data }, { db }) => {
return db.updateOne('members', id, data);
},
deleteMember: async(obj, {id}, { db }) => {
const result = await db.removeOne('members', id);
return result._id;
},
}
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
// Create the default graphql POST route
app.post('/graphql', async (req, res) => {
const conn = await Datastore.open();
const response = await graphql({
schema,
source: req.body.query,
contextValue: { db: conn }
});
res.json(response);
});
export default app.init(); // Bind functions to the serverless cloud
```
## Deployment
You can deploy the code with this [CLI command](/docs/cli#deploy):
```bash
coho deploy
```
## Test the deployed GraphQL endpoint
Test your deployed code with [Postman using GraphQL support](https://www.postman.com/graphql) (recommended).
Postman reads the schema from the codehooks.io `/graphql` endpoint and gives you code completion.
:::tip
Use the CLI command `coho info` to see the HTTP endpoint and the API tokens for the project space.
:::
## You can also use cURL to use the GraphQL API to add and read data
### Add one member mutation
```bash
curl --location --request POST 'https://graphqltest-4pmq.api.codehooks.io/dev/graphql' \
--header 'x-apikey: 66e33428-45d3-4811-be21-58fa6d5d5e91' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"mutation { addMember(name: \"Mr Codehook\", age: 33) { id name }}"}'
```
### Get all members query
```bash
curl --location --request POST 'https://graphqltest-4pmq.api.codehooks.io/dev/graphql' \
--header 'x-apikey: 66e33428-45d3-4811-be21-58fa6d5d5e91' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"{allMembers {id name }}"}'
```
## Use a local serverless server instead of deploying
If you don't want to create an account yet, you can also test your codehooks.io GraphQL backend locally using this command:
```bash
coho localserver
```
## Conclusion
We've demonstrated how you can set up a simple serverless GraphQL server using very little JavaScript code. We hope this has inspired you to check out what is possible to do with codehooks.io's compact backend and database.
To learn more about creating backends with Node.js and codehooks.io, check out the documentation or the blog
---
## Hello world! The simple serverless backend example
*(Note: This content contains MDX/JSX code)*
## 3 simple steps to create your first serverless backend example
1. Create a project with the `coho create` CLI command.
2. Change to the project directory and install `npm i codehooks-js -s`.
3. Open `index.js` in your favorite code editor.
```js title=index.js
import app from 'codehooks-js'
app.get('/hello', async (req, res) => {
console.log("Inside the routehook");
res.json({"message": "Hello world!"});
});
export default app.init();
```
Deploy the serverless backend example with the `coho deploy` CLI command.
Run `coho info --examples` to see your API token and cURL examples.
Test API with:
```js
curl https://.api.codehooks.io/dev/hello -H 'x-apikey: '
```
Test the serverless backend example:
```js
curl https://mybackendproject-x23a.api.codehooks.io/dev/hello -H 'x-apikey: 6e111ea6-6b3a-412d-8f6b-061c816c67c8'
```
:::tip
By clicking the "Import" button in [Postman](https://postman.com) and pasting in the cURL expression, you can test the serverless backend example there.
:::
---
## Mailgun integration example
*(Note: This content contains MDX/JSX code)*

In this Mailgun integration example we'll show how easy it is to create and deploy a complete serverless backend that integrates with the Mailgun API for sending email messages to your users.
[Mailgun](https://mailgun.com) is a SaaS (Software as a Service) email delivery and management platform that allows users to send, receive, and track email communications.
Mailgun is often used by developers and businesses to handle bulk email sending and improve email deliverability.
Before you begin, you need to create a new Codehooks project, if you don't have one already.
## Create a new serverless backend project
```bash
# create a new Codehooks project
coho create mailgunexample
# navigate into the directory for the new project
cd mailgunexample
# install dependent npm packages
npm i codehooks-js form-data node-fetch --save
```
## 4 simple steps to integrate with the Mailgun REST API
1. Get your Mailgun API credentials
2. Create secrets in your serverless backend
3. Create the REST API for sending email
4. Deploy your serverless backend
## 1. Get your Mailgun API credentials
Navigate to your account security setting [directly here](https://app.mailgun.com/app/account/security/api_keys). Help information [is available here](https://help.mailgun.com/hc/en-us/articles/203380100-Where-Can-I-Find-My-API-Key-and-SMTP-Credentials-).
You'll need two secret keys from your Mailgun account: `MAILGUN_APIKEY` and `MAILGUN_DOMAIN`
After locating and inspecting your API-key and domain, protect their content by adding them as encrypted secret environment variables to your serverless backend.
## 2. Create secrets in your serverless backend
It's easy to [add secrets](/docs/application-secrets) as environment variables using the CLI command `set-env`.
The example below shows how to create the two secrets we need to access the Mailgun REST API:
```bash
coho set-env "MAILGUN_APIKEY" "XXXXXXXXXXX" --encrypted
coho set-env "MAILGUN_DOMAIN" "example.com" --encrypted
```
Once the secrets have been added to the serverless runtime we can access them as regular system variables, e.g `process.env.MAILGUN_APIKEY`.
## 3. Create the REST API for sending email
The code example below shows a complete serverless backend application.
The application exposes a REST API endpoint with a `POST` function that calls the Mailgun REST API to send an email to the `body.email` address. We also use the built-in [serverless NoSQL database](/docs/nosql-database-api) to store a log of all sent emails.
```js title="index.js"
/*
* Mailgun integration example.
*/
import {app, Datastore} from 'codehooks-js';
import FormData from 'form-data';
import fetch from 'node-fetch';
// Mailgun REST API endpoint address
const MAILGUN_URL = 'api.eu.mailgun.net'; // or api.mailgun.net for US customers
// REST API for sending email to list of recipients
app.post('/sendmail', async function (req, res) {
// pick from post
const {email, name} = req.body;
// create an email as form data
const form = new FormData();
form.append('from', 'jones@codehooks.io');
form.append('to', email);
form.append('subject', 'Testing Mailgun with Codehooks');
form.append('text', `Hello ${name}, hope you are ok. Lets meet soon.`);
// Mailgun api endpoint
const url = `https://${MAILGUN_URL}/v3/${process.env.MAILGUN_DOMAIN}/messages`;
// Mailgun credentials must be base64 encoded for Basic authentication
const credentials = Buffer.from(`api:${process.env.MAILGUN_APIKEY}`).toString('base64');
// POST REST API with the email form data
const resp = await fetch(url, {
method: 'POST',
headers: {
"Authorization": `Basic ${credentials}`
},
body: form
});
// handle response errors or OK data
if (resp.status <= 201) {
// Success, return Mailgun response to the REST API client
const output = await resp.json();
console.log("Success", output);
// insert log to the NoSQL database
const db = await Datastore.open();
const doc = await db.insertOne('maillog', {email, name, output});
return res.status(201).json(output);
} else {
console.error(rest.status, resp.statusText);
// pass the Mailgun error to the REST API client
return res.status(resp.status).json({error: resp.statusText});
}
})
// bind to serverless runtime
export default app.init();
```
## 4. Deploy your serverless backend
I just love to deploy serverless backends to the Codehooks.io cloud service, it's so easy and instant.
From the project directory run the CLI command for deployment:
```bash
coho deploy
# example output
Project: mailgunexample-8jto Space: dev
Deployed Codehook successfully! π
```
The serverless backend API is now live and available for traffic in the cloud. It you run the CLI command [`coho info --examples`](/docs/cli#info) you will see your backend endpoint address and some curl examples to get you started with testing.
## Testing your Mailgun integration
[Postman](https://www.postman.com/api-platform/api-testing/), [Thunder](https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client) or [curl](https://curl.se) are great tools for API development and testing. In this example we'll keep it simple and use curl to test our new serverless API.
```bash
curl --location --request POST 'https://mailgunexample-8jto.api.codehooks.io/dev/sendmail' \
--header 'x-apikey: bb00d714-c4f2-4df7-9ecb-ad2ce4b29362' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Jane",
"email": "jones@restdb.io"
}'
# Mailgun response on success
{"id":"<20230114154601.5d0bb6cd74064224@example.com>","message":"Queued. Thank you."}
```
:::tip
Use the [CLI command](/docs/cli#logs) `coho logs` to see errors and generally what's going on inside of your runtime application.
:::
Now that our API has processed some traffic, we can [query the NoSQL database](/docs/cli#query) to see what data has been written into the sendmail log collection.
```bash
coho query maillog --pretty
# Database output
{
email: 'jones@restdb.io',
name: 'Jane',
output: {
id: '<20230114161849.9231a6192d685842@mg.codehooks.io>',
message: 'Queued. Thank you.'
},
_id: '185b1139873-p6yxdvkww5jz31'
}
...
```
VoilΓ‘, we have a complete serverless backend for sending emails via the Mailgun REST API, including a full database log of all requests and results.
A more advanced example covering bulk sending, email templates, data import and worker queues can be found in [this blog post](/blog/mailgun-integration).
The full source code from this Mailgun integration example is available at [GitHub](https://github.com/RestDB/codehooks-io-examples/tree/main/mailgunexample).
Happy coding!
---
## Pet store API
*(Note: This content contains MDX/JSX code)*
A simple Pet store API with logging and background jobs and queue processing of complete collection data.
1. Create a project with the `coho create` CLI command.
2. Change to the project directory and install `npm i codehooks-js -s`.
3. Open `index.js` in your favorite code editor and enter the code shown below.
4. Deploy the project with `coho deploy` CLI command π
```js title=index.js
/*
* Pet store API
* Codehooks (c) example code.
* Deploy with CLI command: coho deploy
*/
import {app, Datastore} from 'codehooks-js'
// API endpoint, jobs and queues
app.get('/pet', logStats, getPets);
app.get('/pet/:petid', getPet);
app.post('/pet', logStats, postPet);
app.put('/pet/:petid', putPet);
app.delete('/pet/:petid', deletePet);
app.get('/stats', petStats);
app.job('*/10 * * * * *', cleanUpJob);
app.worker('cleanParrots', cleanParrots);
// Get all pets
async function getPets(req, res) {
console.debug("Get all Pets from datastore");
const conn = await Datastore.open();
conn.getMany('pets').json(res);
}
// Find Pet by ID
async function getPet(req, res) {
const {petid} = req.params;
const conn = await Datastore.open();
const data = await conn.getOne('pets', petid);
res.json(data);
}
// Create a new Pet
async function postPet(req, res) {
const conn = await Datastore.open();
req.body._created = new Date().toISOString();
const doc = await conn.insertOne('pets', req.body);
res.status(201).json(doc);
}
// Update a Pet by ID
async function putPet(req, res) {
const {petid} = req.params;
const conn = await Datastore.open();
const data = await conn.updateOne('pets', petid, req.body);
res.json(data);
}
// Delete a Pet by ID
async function deletePet(req, res) {
const {petid} = req.params;
const conn = await Datastore.open();
const data = await conn.removeOne('pets', petid);
res.json(data);
}
// Add all Parrot pets to a clean up queue
async function cleanUpJob(req, res) {
const conn = await Datastore.open();
const query = {type: "Parrot"}; // all Parrots
const topic = 'cleanParrots';
const job = await conn.enqueueFromQuery('pets', query, topic);
console.log(job);
res.end();
}
// Process queue to clean up Parrot names
async function cleanParrots(req, res) {
let {payload} = req.body;
if (payload.type === 'Parrot' && payload.name !== 'Polly') {
console.log("Job", payload);
payload.formerlyKnownAs = payload.name;
payload.name = "Polly";
const conn = await Datastore.open();
const data = await conn.updateOne('pets', payload._id, payload);
}
res.end();
}
// Middleware to log stats
async function logStats(req, res, next) {
const conn = await Datastore.open();
const kval = await conn.incr(`pet_${req.method}`, 1);
next();
}
// List all pet keys
async function petStats(req, res) {
const conn = await Datastore.open();
const stat = [];
conn.getAll('pet_')
.on('data', (data) => {
res.json(stat);
}).on('end', () => {
res.json(stat);
})
}
export default app.init();
```
---
## React backend example
*(Note: This content contains MDX/JSX code)*
How do you set up an easy backend for [React](https://reactjs.org/)? In this example we'll create a simple React app front-end with a Node.js codehooks.io backend API and database. The main objective of this example is to learn how to use [codehooks.io](https://codehooks.io) as an API backend for your React app.
The output of the React example app is shown in the screen shot below. The **"Hello React world!"** text and the visitor count is fetched by calling the backend REST API:

Read on to learn how to create a simple React app front-end and backend application.
## Create the React example app
We'll be using the standard [create-react-app](https://create-react-app.dev/) to kick-start our React example app development.
Run the following commands from your command line:
```bash
npx create-react-app react-backend-example
cd react-backend-example
```
> You can also clone the GitHub repo `git clone https://github.com/RestDB/codehooks-io-examples.git`
The boilerplate React example app is now generated in your application directory.
Then open the `src/App.js` file in your favorite code editor (some prefer [VS Code](https://code.visualstudio.com/) and others like [Vim](https://www.vim.org/) π₯ π ) and replace the default generated code with the React code example shown below. If you read the code comments you'll get a pretty good idea of what's going on.
```js title="src/App.js"
import React, { useState, useEffect } from 'react';
import logo from './logo.svg';
import './App.css';
// React backend API endpoint and API token
// REPLACE WITH YOURS
const API_ENDPOINT = 'https://reactback-nyly.api.codehooks.io/dev/hello';
const API_KEY = 'a4679c85-b4c8-49fb-b8ac-63230b269dd7';
function App() {
// Application state variables
const [visits, setVisits] = useState(null);
const [message, setMessage] = useState(null);
useEffect(()=>{
// Call Codehooks backend API
const fetchData = async () => {
const response = await fetch(API_ENDPOINT, {
method: "GET",
headers: { "x-apikey": API_KEY }
});
const data = await response.json();
// Change application state and reload
setMessage(data.message);
setVisits(data.visits);
}
fetchData();
},[])
return (
React backend with Codehooks.io
{message || ''}
Visitors: {visits || '---'}
);
}
export default App;
```
The React app is almost ready, we just have to create the backend API and database that serve the content of the `message` and `visits` variables.
## Create the React backend API
The React backend JavaScript example code is located in the `index.js` file in the `reactback` directory. The complete code (14 lines and zero config) for the backend API and database is shown below:
```js title="reactback/index.js"
// React backend example app
import { app, Datastore } from 'codehooks-js'
// REST API
app.get('/hello', async (req, res) => {
const db = await Datastore.open();
// increment visit counter in key-value database
const visits = await db.incr('hits', 1);
res.json({ "message": "Hello React world!", "visits": visits });
});
// bind to serverless runtime
export default app.init();
```
Now that we have the code for the backend API, we can create our new backend with the `init` [CLI command](/docs/cli).
[Sign up](https://account.codehooks.io/login?signup) for a new account and create your first project.
Next you need to install the CLI.
```bash
npm install -g codehooks
```
Then run the init command, this lets you select a project and environment for your new backend code.
```bash
cd reactback
coho init
```
This command creates a new backend application in the `reactback` directory, replace the generated index.js code with our code from the above index.js.
Before we can deploy our React backend to the cloud, we must add our npm package dependencies, in this simple example we just have to add the [codehooks-js](https://www.npmjs.com/package/codehooks-js) package:
```bash
npm i codehooks-js
# deploy the backend application to the codehooks.io cloud service
coho deploy
```
We also need a read-only `API_KEY` key for secure access to our API from the React example app, the `add-token` command fixes this:
```bash
coho add-token --readonly
```
:::tip
You can also add tokens, set environment variables and more for the backend using the UI at [account.codehooks.io](https://account.codehooks.io)
:::
To view our API endpoint and API token(s) we run the `info` command. If we also add the `--examples` parameters the CLI will generate example `curl` commands to test our API π€. Command example output is shown below:
```bash
coho info --examples
Project name: reactback-nyly
Team: Eric Jones (personal account)
API endpoint: https://reactback-nyly.api.codehooks.io/dev
Examples in cURL:
curl https://reactback-nyly.api.codehooks.io/dev/myroute -H 'x-apikey: a4679c85-b4c8-49fb-b8ac-63230b269dd7' -H 'Content-Type: application/json'
curl --request POST https://reactback-nyly.api.codehooks.io/dev/myroute -H 'x-apikey: ' -H 'Content-Type: application/json' --data-raw '{"name": "Mr. Code Hooks", "active": true }'
Spaces:
ββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββ¬βββββββ¬βββββββ
β Name β Tokens β Allowed Hosts β Jwks β Env β
ββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββΌβββββββΌβββββββ€
β dev (active) β a4679c85-b4c8-49fb-b8ac-63230b269dd7 (R) β β β β
ββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββ΄βββββββ΄βββββββ
```
Replace the following variables in the React `App.js` file with yours from the `info` command output:
```js
const API_ENDPOINT = 'https://.api.codehooks.io/dev/hello';
const API_KEY = '';
```
## Start your React app
We are now ready to start the React example app. From the root directory of your React app run the npm start command:
```bash
npm start
# your app runs at http://localhost:3000/
```
## Changing your React backend code is easy
Having the data pipeline up and ready between the React app and the backend API makes it easy to make changes and re-deploy your application. If you make changes in the backend code, there is only 1 step you need to do:
1. From the backend app directory `reactback`, run the `coho deploy` command again and the changes to your React backend API are applied immediately π₯
## Summary
Creating a React front-end and backend application is quite easy. Using your favorite tools together with effective CLI commands is a good workflow that supports rapid iterations and faster development.
Full source code is [available on GitHub here](https://github.com/RestDB/codehooks-io-examples/tree/main/react-backend-example).
## Further reading
For a more comprehensive React example app, check out this this blog post on how to create a [serveless quote api](/blog/serverless-quotes-api). The blog post shows how you can create a React example backend with a large dataset and fast data lookups. For real use cases, you probably don't want to use API keys, but use a service like [Auth0](https://auth0.com) and JWT tokens. Check out our blog post about [how to authenticate a React app against a serverless backend API](/blog/how-to-authenticate-a-react-app-against-a-serverless-backend-api-with-auth0) for details.
Happy coding!
---
## Typescript support
*(Note: This content contains MDX/JSX code)*
Codehooks.io supports Typescript (version 5). This example shows how easy it is to use Typescript to create serverless functions.
1. Start with creating a new project using the `coho create` or `coho init` CLI command.
2. Change to the project directory and install `npm i codehooks-js`.
3. Rename the autogenerated `index.js` file to `index.ts`. That's it. You are now using Typescript as a language.
In the following example we create two files; `index.ts` with two serverless API functions and `sum.ts` with one exported function.
```js title=index.ts
import {app, httpRequest, httpResponse} from 'codehooks-js';
import { sum } from './sum';
function helloFunc(req: httpRequest, res: httpResponse){
res.end(`Typescript sum is ${sum(42, 42)}`);
}
app.get('/helloworld', helloFunc);
app.all('/echo', (req: httpRequest, res: httpResponse) => {
res.end(JSON.stringify(req, null, " ")); // format JSON string nicely
})
export default app.init();
```
```js title=sum.ts
export function sum(a: number, b: number): number {
return a + b;
}
```
Deploy with the CLI command `coho deploy`.
Run `coho info` to see your API token.
## Testing the helloworld API with curl
```js title="Shell command"
curl https://.api.codehooks.io/dev/helloworld -H 'x-apikey: '
```
```bash title="Output"
Typescript sum is 84
```
## Testing the echo API with curl
```js title="Shell command"
curl https://.api.codehooks.io/dev/echo -H 'x-apikey: '
```
```js title="Example output"
{
"hostname": "myproject-abcd.api.codehooks.io",
"headers": {
"host": "myproject-abcd.api.codehooks.io",
"user-agent": "curl/7.77.0",
"accept": "*/*",
"x-apikey": "6e111ea6-6b3a-412d-8f6b-061c816c67c8"
},
"query": {},
"path": "/dev/echo",
"apiPath": "/echo",
"originalUrl": "/dev/echo",
"params": null,
"body": {},
"method": "GET"
}
```
---
## File API
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
The File API enables your application to access and manage folders and files.
The API is automatically available from the inside of any Codehook function.
**Install**
Run `npm install codehooks-js` in your project code folder.
:::info Bulk upload files
Use the CLI command [coho upload](/docs/cli#file-upload) to upload files from a local machine to the application.
:::
**API quick overview**
## readFile(path)
Read file content as text from the file system.
**Parameters**
- **path**: full path to file, e.g. '/documents/doc.html'
**Returns**: Promise with text content from file.
**Error**: throws exception if file is not found.
**Code example for reading a file**
```js {6} title="index.js"
import { app, filestore } from 'codehooks-js';
// get a file text content
app.get('/txt', async (req, res) => {
try {
const file = await filestore.readFile('/somefolder/myfile.txt');
res.set('content-type', 'text/plain');
res.end(file);
} catch (error) {
res.status(404).end('No file here');
}
});
// bind to serverless runtime
export default app.init();
```
## getReadStream(path)
Read file content as a binary stream the file system.
**Parameters**
- **path**: full path to file, e.g. `/private/dummy.pdf`
**Returns**: Promise with file stream handle.
**Error**: throws exception if file is not found.
**Code example for reading a binary file stream**
```js {6} title="index.js"
import { app, filestore } from 'codehooks-js';
// get a file binary content as stream
app.get('/pdf', async (req, res) => {
try {
const filestream = await filestore.getReadStream('/private/dummy.pdf');
res.set('content-type', 'application/pdf');
// stream content back to client
filestream
.on('data', (buf) => {
res.write(buf, 'buffer');
})
.on('end', () => {
res.end();
});
} catch (error) {
console.error(error);
res.status(404).end('No file here');
}
});
// bind to serverless runtime
export default app.init();
```
## saveFile(path, filestream)
Write a file binary stream to the file system.
**Parameters**
- **path**: full path to file, e.g. `/private/dummy.pdf`
- **filestream**: inputstream of binary buffers
**Returns**: Promise with upload result text.
**Error**: throws exception if an error occured.
**Code example for writing a binary inputstream as a new file to the file system**
```js {10} title="index.js"
import { app, filestore } from 'codehooks-js';
import { PassThrough } from 'stream';
// post a file binary content
app.post('/file', async (req, res) => {
try {
const { name } = req.query; /* e.g. /dev/file?name=/upload/dummy.pdf */
const stream = new PassThrough();
req.pipe(stream);
const result = await filestore.saveFile(name, stream);
res.end(result);
} catch (error) {
console.error(error);
res.status(404).end('Upload error');
}
});
// bind to serverless runtime
export default app.init();
```
## deleteFile(path)
Delete a file binary from the file system.
**Parameters**
- **path**: full path to file, e.g. `/private/dummy.pdf`
**Returns**: Promise with delete result text.
**Error**: throws exception if an error occured.
**Code example for deleting a file from the file system**
```js {7} title="index.js"
import { app, filestore } from 'codehooks-js';
// delete a file
app.delete('/file', async (req, res) => {
try {
const { name } = req.query;
const result = await filestore.deleteFile(name);
res.end(result);
} catch (error) {
console.error(error);
res.status(404).end('Not found');
}
});
// bind to serverless runtime
export default app.init();
```
## list(path)
List files in folders from the file system.
**Parameters**
- **path**: full path to folder, e.g. `/upload`
**Returns**: Promise with JSON array of files matching path to folder.
**Error**: throws exception if an error occured.
**Code example for listing files in a folder from the file system**
```js {6} title="index.js"
import { app, filestore } from 'codehooks-js';
// list dir
app.get('/list', async (req, res) => {
try {
const result = await filestore.list('/');
res.end(result);
} catch (error) {
console.error(error.message);
res.status(400).end('sorry list');
}
});
// bind to serverless runtime
export default app.init();
```
**Example output shows folders and files**:
```title="JSON array"
[
{"path":"/robots.txt","size":114,"date":"2023-09-29T12:12:10.000Z","_id":"6516f3b26c816e0015ca7b60"},
{"path":"/upload/dummy.pdf","size":1234,"date":"2023-09-29T16:22:10.499Z","_id":"6516f9b26c816e0015ce7b6d"},
{"path":"/upload/logo.pdf","size":259096,"date":"2023-09-30T11:48:18.003Z","_id":"6517155cd8c8a40071d6bc79"},
{"path":"/upload/report.html","size":1320,"date":"2023-09-30T12:46:31.640Z","_id":"65180b50d8fc2b01b3b2515e"}
]
```
## readFileAsBuffer(path)
Read file content into a Buffer.
**Parameters**
- **path**: full path to folder, e.g. `/upload`
**Returns**: Promise with Buffer.
**Error**: throws exception if an error occured.
**Code example for reading file content into a Buffer**
```js
// collapsed code
const buf = await filestore.readFileAsBuffer('/images/logo.png');
console.log(buf.length);
```
## Postman examples using the File API
### Upload a new PDF file

### Download a PDF file

---
## Indexing API
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
Create fast lookup indexes in a datastore. Combined with [streaming queries](nosql-database-api#getmanycollection-options), indexing can be a big improvement for your application performance.
**API quick overview**
## Datastore.open()
Datastore.open() opens a connection to a datastore in the active project space.
```js
import {Datastore} from 'codehooks-js'
async function myfunc() {
const conn = await Datastore.open();
// use conn to call API functions
...
}
```
**Returns** Promise / Datastore connection
## createIndex(collection, indexes)
Create index(es) on a collection in the current Datastore
**Parameters**
- **collection**: collection string
- **indexes**: Array of field names to index
Returns **Promise / Indexes**
**Code example**
```js
const conn = await Datastore.open();
const idx = await conn.createIndex('stocks', ['Name', 'Volume']);
```
:::warning
Index creation can take a long time on big collections.
It's better to create indexes before adding data.
:::
## removeIndex(collection, indexes)
Remove index(es) on a collection in the current Datastore
**Parameters**
- **collection**: collection string
- **indexes**: Array of field names to remove from index
Returns **Promise / Indexes**
**Code example**
```js
const conn = await Datastore.open();
const idx = await conn.removeIndex('stocks', ['Name', 'Volume']);
```
:::tip
You can also use the [CLI](cli#createindex) to manage indexes.
:::
---
## Job background workers
*(Note: This content contains MDX/JSX code)*
Job workers lets you schedule background worker functions as recurring `cron` jobs or as one-time `runAt` jobs. When a job event _triggers_, your function is called accordingly.
## Cron jobs
### Example cron jobhook
The example job worker function prints to the log each time the cron triggers.
```js
import app from 'codehooks-js';
app.job('* * * * *', minute);
app.job('*/30 * * * *', halfPast);
function minute(req, res) {
console.log('Each minute');
res.end(); // done
}
function halfPast(req, res) {
console.log('Each half hour');
res.end(); // done
}
export default app.init();
```
:::info tip
Utility to create CRON expressions [https://crontab.guru/](https://crontab.guru/)
:::
- Running jobs are stopped and re-started on each deployment (`coho deploy`)
- Running jobs are stopped on undeployment (`coho undeploy`)
### Cron Syntax
Quick reference to cron syntax.
```
βββββββββββββββ second (optional) 0-59
β βββββββββββββ minute 0-59
β β βββββββββββ hour 0-23
β β β βββββββββ day of month 1-13
β β β β βββββββ month 1-12
β β β β β βββββ day of week 0-7 (0 or 7 are sunday)
β β β β β β
β β β β β β
* * * * * *
```
### Example cron expressions
**Run five minutes after midnight, every day**
`5 0 * * *`
**Run at 2:15pm on the first of every month**
`15 14 1 * *`
**Run at 10 pm on weekdays**
`0 22 * * 1-5`
## Schedule delayed worker function dynamically with _runAt_
Cron jobs are declare at deploy-time and they run forever, or until they are changed or removed by a new deployment.
The `schedule.runAt` API is another way to create a one time job that is executed by a worker function.
### Example runAt scheduled worker function
The example worker function prints the data payload to the log each time the scheduled datetime triggers.
```js {4,17}
/*
* runAt example.
*/
import { app, schedule } from 'codehooks-js';
app.worker('myworker', (data, job) => {
const { payload } = data.body;
console.log('Delayed job', payload);
job.end();
});
// REST hook
app.get('/hello', async (req, res) => {
const when = new Date(Date.now() + 5000);
const data = { message: 'Hello later!' };
const worker = 'myworker';
await schedule.runAt(when, data, worker);
res.end();
});
// bind to serverless runtime
export default app.init();
```
If we call the `/hello` API the `coho logs` command will display the following:
```bash
dev 2023-03-23T17:37:54.198Z Delayed job {
"message": "Hello later!"
}
dev 2023-03-23T17:37:54.940Z Delayed job {
"message": "Hello later!"
}
dev 2023-03-23T17:37:55.652Z Delayed job {
"message": "Hello later!"
}
...
```
## Schedule worker function to run immediate
You can also execute a worker function without any delay like this:
```js
...
schedule.run({data}, 'myworker')
...
```
---
## Key-Value Store API
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
With the Key-Value Store API, you can run Redis-like operations against a Key-Value database.
This API automatically available from the inside of any Codehook function.
:::tip Check out the Key-Value Store tutorial - 7 parts
- [Part 1: What is a Key-Value Store and how do you use it?](/docs/tutorials/key-val-store/part-1)
- [Part 2: Basic operations](/docs/tutorials/key-val-store/part-2-basic-operations)
- [Part 3: Increment and decrement operations](/docs/tutorials/key-val-store/part-3-increment-and-decrement-operations)
- [Part 4: Working with multiple values and streams](/docs/tutorials/key-val-store/part-4-working-with-multiple-values-and-streams)
- [Part 5: Managing data with TTL options](/docs/tutorials/key-val-store/part-5-managing-data-with-ttl-options)
- [Part 6: Multiple key spaces](/docs/tutorials/key-val-store/part-6-multiple-key-spaces)
- [Part 7: Interaction with the key-val store from the CLI](/docs/tutorials/key-val-store/part-7-key-value-store-interaction-from-cli)
:::
**API quick overview**
## Datastore.open()
Datastore.open() opens a connection to a datastore in the active project space.
```js
import {Datastore} from 'codehooks-js'
async function myfunc() {
const conn = await Datastore.open();
// use conn to call API functions
...
}
```
**Returns** a Promise with a Datastore connection
## set(key, value, options)
Set a key-value **string** pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **value**: Any string value
- **options**: (optional)
- **ttl**: Time to live in milliseconds
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result string value
**Code example using a string value**
```js
const ONE_DAY = 86400000; // 1000 * 60 * 60 * 24
const conn = await Datastore.open();
const opt = {
ttl: ONE_DAY,
keyspace: 'sessions',
};
const result = await conn.set(
'my_special_key',
'f439a2d4-d21b-44ea-97ac-f210120f10f3',
opt
);
```
## setObj(key, value, options)
Set a key-value **string / Object** pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **value**: Any JSON Object value
- **options**: (optional)
- **ttl**: Time to live in milliseconds
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result string value
**Code example using a JSON Object**
```js
const ONE_DAY = 86400000; // 1000 * 60 * 60 * 24
const conn = await Datastore.open();
const opt = {
ttl: ONE_DAY,
keyspace: 'sessions',
};
const result = await conn.setObj(
'my_special_key',
{
session_id: 'f439a2d4-d21b-44ea-97ac-f210120f10f3',
user_id: '1234567890',
},
opt
);
```
## get(key, options)
Get a key-value **string** pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **options**: (optional)
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result string value
**Code example using a string value**
```js
const conn = await Datastore.open();
const opt = {
keyspace: 'sessions',
};
const kval = await conn.get('my_special_key', opt);
console.log('key-val', kval);
```
## getObj(key, options)
Get a key-value **string / Object** pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **options**: (optional)
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result JSON Object
**Code example using a JSON Object**
```js
const conn = await Datastore.open();
const opt = {
keyspace: 'sessions',
};
const { session_id, user_id } = await conn.getObj('my_special_key', opt);
console.log('key-val', session_id, user_id);
```
## getAll(keypattern, options)
Get all key-value pair that matches keypattern in a datastore.
**Parameters**
- **keypattern**: Key pattern matches key starting with **string**
- **options**: (optional)
- **keyspace**: Name of isolated keyspace
Returns **Promise / JSON object stream**
**Code example**
```js
const conn = await Datastore.open();
const opt = {
keyspace: 'sessions',
};
const stream = await conn.getAll('my_special_', opt);
stream
.on('data', (data) => {
console.log(data);
})
.on('end', () => {
console.log('The end');
});
```
## incr(key, number, options)
Increment a key-value pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **number**: Value to increment >= 1
- **options**: (optional)
- **ttl**: Time to live in milliseconds
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result string value
**Code example**
```js
const conn = await Datastore.open();
const kval = await conn.incr('counter', 1);
console.log('key-val', kval);
```
## decr(key, number, options)
Decrement a key-value pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **number**: Value to decrement <= 1
- **options**: (optional)
- **ttl**: Time to live in milliseconds
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result string value
**Code example**
```js
const conn = await Datastore.open();
const kval = await conn.decr('counter', 1);
console.log('key-val', kval);
```
## del(key, options)
Delete a key-value pair in a datastore.
**Parameters**
- **key**: Unique key in keyspace
- **options**: (optional)
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result string value
**Code example**
```js
const conn = await Datastore.open();
const kval = await conn.del('my_special_key');
console.log('key-val', kval);
```
## delAll(key, options)
Delete a range of key-value pairs in a datastore.
**Parameters**
- **keypattern**: Key pattern in keyspace that matches start of **string**
- **options**: (optional)
- **keyspace**: Name of isolated keyspace
**Returns** A Promise with result JSON object
**Code example**
```js
const conn = await Datastore.open();
const kval = await conn.delAll('my_special_');
console.log('key-val', kval);
```
---
## Database API
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
The Database API provides a powerful and flexible interface for interacting with the built-in NoSQL datastore in your Codehooks project. This API allows you to perform a wide range of operations, from basic CRUD (Create, Read, Update, Delete) to more advanced querying and data manipulation.
## Key Features
- **NoSQL Flexibility**: Work with schema-less data structures for maximum adaptability.
- **CRUD Operations**: Easily insert, retrieve, update, and delete documents.
- **Advanced Querying**: Use MongoDB-style query language for complex data retrieval.
- **Data Streaming**: Efficiently handle large datasets with streaming capabilities.
- **Schema Validation**: Optionally enforce data structure with JSON Schema.
- **Collection Management**: Programmatically create and manage database collections.
## Getting Started
To use the Database API, you'll first need to open a connection to the datastore:
```js
import { Datastore } from 'codehooks-js';
async function myDatabaseFunction() {
const conn = await Datastore.open();
// Use conn to interact with the database
// ...
}
```
Once you have a connection, you can use various methods to interact with your data. Here's a quick overview of some common operations:
- `insertOne()`: Add a new document to a collection
- `getOne()`: Retrieve a single document by ID or query
- `getMany()`: Retrieve multiple documents matching a query
- `updateOne()`: Modify a single document
- `removeOne()`: Delete a single document
For more detailed information on each method and advanced usage, refer to the specific sections below.
**API quick overview**
## Datastore.open()
Datastore.open() opens a connection to a datastore in the active project space.
```js
import {Datastore} from 'codehooks-js'
async function myfunc() {
const conn = await Datastore.open();
// use conn to call API functions
...
}
```
**Returns** Promise / Datastore connection
## insertOne(collection, document)
Insert a new data object into a collection in a datastore.
**Parameters**
- **collection**: collection name
- **document**: JSON document or array of JSON documents
**Returns:** Promise with the new JSON document and an unique `_id` value.
**Validation exceptions:** [See JSON-schema validation](#data-validation)
**Code example**
```js {6}
import { app, Datastore } from 'codehooks-js';
async function createSale(req, res) {
const conn = await Datastore.open();
try {
const doc = await conn.insertOne('sales', req.body);
// return new document to client
res.status(201).json(doc);
} catch (error) {
if (error.schemaError) {
// JSON-schema validation error
res.status(400).json(error);
} else {
// unknown error
res.status(500).json({ error: error.message });
}
}
}
// Serverless route hook
app.post('/sales', createSale);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
POST
https://myproject-ff00.api.codehooks.io/dev/sales
Example input:
{
"Region": "North America",
"Item Type": "Cereal",
"Volume": 200
}
Example output:
{
"_id": "640f60e2b4fdc00015c4280f",
"Region": "North America",
"Item Type": "Cereal",
"Volume": 200
}
```
## getOne(collection, ID | Query)
Get one data object by ID or by Query from a collection in a datastore.
**Parameters**
- **collection**: name of collection
- **ID** or **Query**: the `_id` value, or a Query `{value: "some value"}`
**Returns** Promise with json document
**Code example**
```js {7}
import { app, Datastore } from 'codehooks-js';
async function getOne(req, res) {
try {
const conn = await Datastore.open();
const { id } = req.params;
const data = await conn.getOne('customer', id); // same as conn.getOne('customer', {_id: id})
res.json(data);
} catch (error) {
// not found
res.status(404).json({ error: error.message });
}
}
// Serverless route hook
app.get('/cust/:id', getOne);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
GET
https://myproject-ff00.api.codehooks.io/dev/cust/640f60f2b4fdc00015c42811
Example output:
{
"name": "Acme Inc.",
_id: "640f60f2b4fdc00015c42811"
}
```
## findOne(collection, ID | Query)
Alias for [getOne](#getonecollection-id--query)
## find(collection, query, options)
Alias for [getMany](#getmanycollection-query-options)
## getMany(collection, query, options)
Stream multiple data objects from a collection in a datastore.
Data are sorted naturally by the index for the collection, default index is by `_id` which will stream data in chronological order.
**Parameters**
- **collection**: collection name (mandatory)
- **query**: mongoDB query object, e.g. `{name: "Jane"}` [(Read query language docs)](nosql-database-query-language). Default query is empty `{}`, i.e. all objects
- **options** (optional settings)
- **sort**: mongoDB sort object. E.g. sort by name ascending `{"name": 1}`
- **limit**: how many items to return
- **hints**: include or omit fields
- Include fields example: `{$fields: {name: 1, address: 1}}`
- Omit fields example: `{$fields: {name: 0, _id_: 0}}`
- **offset**: how many items to skip before returning any
**Returns** a JSON array data stream.
### Simple code example
```js {5}
import { app, Datastore } from 'codehooks-js';
async function getSales(req, res) {
const conn = await Datastore.open();
conn.getMany('sales').json(res); // pipe json stream to client
}
// Serverless route hook
app.get('/sales', getSales);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
GET
https://myproject-ff00.api.codehooks.io/dev/sales
Example output:
[
{
"Region": "France",
"Item Type": "Veal",
"Volume": 130,
"Unit Price": 2005.7,
"Unit Cost": 11s7.11,
"_id": "640f60e2b4fdc00015c4280f"
},
{
"Region": "Germany",
"Item Type": "Haxe",
"Volume": 30,
"Unit Price": 30.4,
"Unit Cost": 32.43,
"_id": "640f60e2b4fdc00015c4282f"
}
]
```
### Advanced code example
```js {10}
import { app, Datastore } from 'codehooks-js';
async function getSales(req, res) {
const conn = await Datastore.open();
const query = { Volume: { $gt: req.params.vol } };
const options = {
limit: 1000,
hints: { $fields: { status: 0, _id: 0 } },
};
conn.getMany('sales', query, options).json(res); // json stream is piped back to client
}
// Serverless route hook
app.get('/sales/:vol', getSales);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
GET
https://myproject-ff00.api.codehooks.io/dev/sales/1000
Example output:
[
{
"Region": "North America",
"Item Type": "Cereal",
"Volume": 1304,
"Unit Price": 205.7,
"Unit Cost": 117.11
},
{
"Region": "North America",
"Item Type": "Cereal",
"Volume": 1306,
"Unit Price": 10.3,
"Unit Cost": 3.3
}
]
```
### Streaming data code example
With the steaming API you're in full control over how to deliver data back to a client.
In this example the `content-type` is set first, next the code is using `forEach` to iterate the stream to output one line of data per database object.
```js {8,10}
import { app, Datastore } from 'codehooks-js';
async function getSales(req, res) {
res.set('content-type', 'text/plain');
const conn = await Datastore.open();
let count = 0;
const cursor = conn.getMany('sales', { 'Item Type': 'Cereal' });
await cursor.forEach((item) => {
// use the response.write to stream data back to client
res.write(`Item ${count++} has volume ${item.Volume}\n`);
});
res.end();
}
// Serverless route hook
app.get('/sales', getSales);
export default app.init(); // Bind functions to the serverless runtime
```
Output from the streaming data code example:
```
Item 0 has volume 1304
Item 1 has volume 1306
...
```
## JSONL (JSON Lines) Data Format
JSONL (JSON Lines) is a convenient format for storing structured data that can be processed one record at a time. Each line is a valid JSON value, making it perfect for streaming large datasets efficiently.
### Why JSONL Matters for Scale and Speed
**Streaming Efficiency**: JSONL allows processing of large datasets without loading everything into memory at once. This is crucial for applications dealing with millions of records.
**Real-time Processing**: Each JSON object can be processed immediately as it's received, enabling real-time data pipelines and analytics.
**Memory Optimization**: Unlike traditional JSON arrays that require parsing the entire document, JSONL can be processed line-by-line, significantly reducing memory footprint.
**Parallel Processing**: JSONL files can be easily split and processed in parallel across multiple workers or nodes.
### AI/LLM/MCP Applications
The rise of Large Language Models (LLMs), AI applications, and Model Context Protocols (MCP) has made JSONL increasingly important:
- **Training Data**: LLMs often require massive datasets in JSONL format for fine-tuning and training
- **Inference Pipelines**: AI applications need to process large volumes of data for real-time inference
- **MCP Integration**: Model Context Protocols often expect streaming data formats for efficient data exchange
- **Vector Databases**: Many vector databases and embedding services work optimally with JSONL streams
- **Batch Processing**: AI workloads frequently involve processing large batches of documents or records
### JSONL Streaming Example
Here's how to stream data in JSONL format with the correct content-type header:
```js {8,10}
import { app, Datastore } from 'codehooks-js';
async function getSalesJSONL(req, res) {
// Set content-type for JSONL/NDJSON format
res.set('content-type', 'application/x-ndjson');
const conn = await Datastore.open();
const query = { Region: 'North America' };
const cursor = conn.getMany('sales', query);
// Stream each document as a separate JSON line
await cursor.forEach((item) => {
// Each line is a complete JSON object followed by newline
res.write(JSON.stringify(item) + '\n');
});
res.end();
}
// Serverless route hook
app.get('/sales-jsonl', getSalesJSONL);
export default app.init(); // Bind functions to the serverless runtime
```
**Example output**:
```json
{"Region": "North America", "Item Type": "Cereal", "Volume": 1304, "Unit Price": 205.7, "Unit Cost": 117.11, "_id": "640f60e2b4fdc00015c4280f"}
{"Region": "North America", "Item Type": "Beverages", "Volume": 856, "Unit Price": 10.3, "Unit Cost": 3.3, "_id": "640f60e2b4fdc00015c4282f"}
{"Region": "North America", "Item Type": "Snacks", "Volume": 2341, "Unit Price": 15.8, "Unit Cost": 8.2, "_id": "640f60e2b4fdc00015c4283f"}
```
### Advanced JSONL with Filtering and Transformation
For AI/LLM applications, you might need to transform or filter data before streaming:
```js {12,15}
import { app, Datastore } from 'codehooks-js';
async function getProcessedDataJSONL(req, res) {
res.set('content-type', 'application/x-ndjson');
const conn = await Datastore.open();
const query = { Volume: { $gt: 1000 } }; // Only high-volume items
const options = {
sort: { Volume: -1 }, // Sort by volume descending
hints: { $fields: { Region: 0, _id: 0 } }, // Exclude certain fields
};
const cursor = conn.getMany('sales', query, options);
await cursor.forEach((item) => {
// Transform data for AI processing
const processedItem = {
text: `${item['Item Type']} with volume ${item.Volume}`,
metadata: {
volume: item.Volume,
unitPrice: item['Unit Price'],
unitCost: item['Unit Cost'],
},
timestamp: new Date().toISOString(),
};
res.write(JSON.stringify(processedItem) + '\n');
});
res.end();
}
app.get('/processed-data-jsonl', getProcessedDataJSONL);
export default app.init();
```
This approach is particularly valuable for:
- **Data Preprocessing**: Preparing datasets for machine learning models
- **Feature Extraction**: Converting raw data into features suitable for AI processing
- **Real-time Analytics**: Streaming processed data to analytics dashboards
- **API Integration**: Providing data to external AI services and LLM APIs
## toArray(collection, query, options)
Utility function to return multiple data objects as an array of objects from the [getMany](#getmanycollection-query-options) API call.
**Simple code example**
```js {6}
import { app, Datastore } from 'codehooks-js';
async function getSales(req, res) {
const conn = await Datastore.open();
const query = { product: 'foo' }; // query for foo products
const array = await conn.getMany('sales', query).toArray(); // as array
// return json array
res.json(array);
}
// Serverless route hook
app.get('/sales', getSales);
export default app.init(); // Bind functions to the serverless runtime
```
## updateOne(collection, ID | Query, updateoperators, options)
Update one data object by ID or by Query in a datastore collection.
Input document is patched with the matched database object.
**Parameters**
- **collection**: name of collection
- **ID** or **Query**: the `_id` value, or a Query `{value: "some value"}`
- **updateoperators**: new json document to update or [manipulate data operators](dataoperators)
- **options**: (optional settings)
- **upsert**: Create a new document if ID does not exist by `{upsert: true}`
**Returns:** Promise with updated json document
**Validation exceptions:** [See JSON-schema validation](#data-validation)
### Code example: update by ID
```js {7}
import { app, Datastore } from 'codehooks-js';
async function myFunc(req, res) {
const { id } = req.params;
const { body } = req;
const conn = await Datastore.open();
const data = await conn.updateOne('customer', { _id: id }, { $set: body });
res.json(data);
}
// Serverless route hook
app.put('/cust/:id', myFunc);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
POST
https://myproject-ff00.api.codehooks.io/dev/cust/640f60f2b4fdc00015c42811
Example input:
{
"name": "Acme Inc.",
"sales": 42
}
```
### Code example: update using the upsert option
```js {17}
// upsert api route
app.post('/update', async (req, res) => {
const { email } = req.body;
console.log('upsert', email);
const conn = await datastore.open();
const upsertResult = await conn.updateOne(
'users',
// query
{
emailAddress: email,
},
// update operators
{
$set: { emailAddress: email },
$inc: { visits: 1 },
},
// upsert option
{ upsert: true }
);
res.json(upsertResult);
});
```
**Example input and result**
```js
POST
https://myproject-ff00.api.codehooks.io/dev/update
# example input:
{
"email": "jane@examle.com"
}
# example output:
{
"emailAddress": "jane@example.com",
"visits": 8,
"_id": "658af419d44110bef4e88beb"
}
```
## updateMany(collection, query, document, options)
Update multiple data objects in a datastore collection.
Input document is patched with the matched database objects.
**Parameters**
- **collection**: name of collection
- **query**: mongoDB query object [(docs)](nosql-database-query-language)
- **document**: new json document to update or [manipulate](dataoperators)
- **options** (optional settings)
**Returns:** Promise with count of objects updated
**Validation exceptions:** [See JSON-schema validation](#data-validation)
**Code example**
```js {12}
import { app, Datastore } from 'codehooks-js';
async function myFunc(req, res) {
const conn = await Datastore.open();
const query = {
customerStatus: 'GOLD',
};
const doc = {
$inc: { bonus: '20' },
$set: { customerStatus: 'PLATINUM' },
};
const data = await conn.updateMany('customer', query, doc);
res.json(data);
}
// Serverless route hook
app.put('/cust', myFunc);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
PUT
https://myproject-ff00.api.codehooks.io/dev/cust
Example output:
[
{
"name": "Acme Inc.",
"customerStatus": "PLATINUM",
"bonus": "20",
_id: "640f60f2b4fdc00015c42811"
},
{
"name": "Colo Inc.",
"customerStatus": "PLATINUM",
"bonus": "30",
_id: "640f60f2b4fdc00015c42f00"
},
...
]
```
## replaceOne(collection, ID | Query, document, options)
Replace one data object by ID or by Query in a datastore collection.
Input document overwrites the matched database object, the `_id` value is not overwritten.
**Parameters**
- **collection**: name of collection
- **ID** or **Query**: the `_id` value, or a Query `{value: "some value"}`
- **document**: new json document to replace
- **options**: (optional settings)
- **upsert**: Create a new document if ID does not exist by `{upsert: true}`
**Returns** Promise with updated json document
**Validation exceptions:** [See JSON-schema validation](#data-validation)
**Code example**
```js {5}
import { app, Datastore } from 'codehooks-js';
async function myFunc(req, res) {
const conn = await Datastore.open();
const data = await conn.replaceOne(
'customer',
{ _id: req.params.id },
req.body
);
res.json(data);
}
// Serverless route hook
app.put('/cust/:id', myFunc);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
POST
https://myproject-ff00.api.codehooks.io/dev/cust/640f60f2b4fdc00015c42811
Example input:
{
"name": "Acme Replacement Inc.",
"sales": 0
}
```
## replaceMany(collection, query, document, options)
Replace multiple data objects in a datastore collection.
Input document overwrites the matched database objects. The `_id` value is not overwritten.
**Parameters**
- **collection**: name of collection
- **query**: mongoDB query object [(docs)](nosql-database-query-language)
- **document**: new json document to replace
- **options** (optional settings)
**Returns** Promise with count of objects updated
**Validation exceptions:** [See JSON-schema validation](#data-validation)
**Code example**
```js {11}
import { app, Datastore } from 'codehooks-js';
async function myFunc(req, res) {
const conn = await Datastore.open();
const query = {
customerStatus: 'GOLD',
};
const doc = {
Acme: 'was here',
};
const data = await conn.replaceMany('customer', query, doc);
res.json(data);
}
// Serverless route hook
app.put('/cust', myFunc);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
PUT
https://myproject-ff00.api.codehooks.io/dev/cust
Example output:
[
{
"Acme": "was here"
_id: "640f60f2b4fdc00015c42811"
},
{
"Acme": "was here"
_id: "640f60f2b4fdc00015c400fe"
},
...
]
```
## removeOne(collection, ID | Query)
Remove one data object by ID or by Query in a datastore collection.
**Parameters**
- **collection**: name of collection
- **ID** or **Query**: the `_id` value, or a Query `{value: "some value"}`
**Returns** Promise with document ID
**Code example**
```js {5}
import { app, Datastore } from 'codehooks-js';
async function myFunc(req, res) {
const conn = await Datastore.open();
const data = await conn.removeOne('customer', { _id: req.params.id });
res.json(data);
}
// Serverless route hook
app.delete('/cust/:id', myFunc);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
DELETE
https://myproject-ff00.api.codehooks.io/dev/cust/640f60f2b4fdc00015c42811
Example output:
640f60f2b4fdc00015c42811
```
## removeMany(collection, query, options)
Remove multiple data objects in a datastore collection.
**Parameters**
- **collection**: name of collection
- **query**: mongoDB query object [(docs)](nosql-database-query-language)
- **options** (optional settings)
**Returns** Promise with count of objects deleted
**Code example**
```js {8}
import { app, Datastore } from 'codehooks-js';
async function myFunc(req, res) {
const conn = await Datastore.open();
const query = {
customerStatus: 'GOLD',
};
const data = await conn.removeMany('customer', query);
res.json(data);
}
// Serverless route hook
app.delete('/cust', myFunc);
export default app.init(); // Bind functions to the serverless runtime
```
**Example input and result**
```js
DELETE
https://myproject-ff00.api.codehooks.io/dev/cust
Example output:
{"count": 3}
```
## Data validation
Add data validation (JSON-schema) to any database collection directly to the database collection using the [CLI command](/docs/cli#add-schema) `add-schema` or the Codehooks Studio application navigating to Collection/Settings/JSON-Schema.

### Example: Defining a JSON Schema using the CLI
To ensure your data is consistent and secure, you can define a [JSON Schema](https://json-schema.org/) for your collections. This schema will validate the data structure and enforce data types.
Create a schema file (e.g., `personSchema.json`) defining the structure of your data:
```js
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "The person's first name."
},
"lastName": {
"type": "string",
"description": "The person's last name."
},
"age": {
"type": "integer",
"description": "The person's age.",
"minimum": 0
},
"email": {
"type": "string",
"format": "email",
"description": "The person's email address."
},
},
"required": [
"firstName",
"lastName",
"email"
]
}
```
Use the CLI to add this JSON Schema to your collection:
```bash
codehooks add-schema --collection 'person' --schema './personSchema.json'
```
### JSON-Schema validation errors and status code
In the event that clients sends data not following the JSON-Schema, a validator error will be thrown.
E.g. when the API posts a JSON object missing a required field a validator `schemaError` error array of objects will be returned.
```js
{
"schemaError": [
{
"instancePath": "",
"schemaPath": "#/required",
"keyword": "required",
"params": {
"missingProperty": "firstName"
},
"message": "must have required property 'firstName'"
}
]
}
```
### Data validation JSON schema API
The following API methods allow you to programmatically manage JSON schemas for your collections, providing an alternative to using the CLI or Codehooks Studio for schema management.
#### setSchema(collection, schema)
Set a JSON schema for a collection to enforce data validation.
**Parameters**
- **collection**: name of the collection
- **schema**: JSON schema object
**Returns** Promise with the result of the operation
**Code example**
```js
import { app, Datastore } from 'codehooks-js';
import { mySchema } from './schema.json';
async function setCollectionSchema(collection) {
const conn = await Datastore.open();
const result = await conn.setSchema(collection, mySchema);
return result;
}
export default app.init(async () => {
try {
const result = await setCollectionSchema('person');
console.log('Schema set successfully:', result);
} catch (error) {
console.error('Error setting schema:', error.message);
}
});
```
#### getSchema(collection)
Retrieve the JSON schema for a collection.
**Parameters**
- **collection**: name of the collection
**Returns** Promise with the JSON schema or null if not set
**Code example**
```js
import { app, Datastore } from 'codehooks-js';
async function getCollectionSchema(req, res) {
const conn = await Datastore.open();
const { collection } = req.params;
try {
const schema = await conn.getSchema(collection);
res.json(schema);
} catch (error) {
res.status(400).json({ error: error.message });
}
}
app.get('/get-schema/:collection', getCollectionSchema);
export default app.init();
```
#### removeSchema(collection)
Remove the JSON schema from a collection.
**Parameters**
- **collection**: name of the collection
**Returns** Promise with the result of the operation
**Code example**
```js
import { app, Datastore } from 'codehooks-js';
async function removeCollectionSchema(req, res) {
const conn = await Datastore.open();
const { collection } = req.params;
try {
const result = await conn.removeSchema(collection);
res.json(result);
} catch (error) {
res.status(400).json({ error: error.message });
}
}
app.delete('/remove-schema/:collection', removeCollectionSchema);
export default app.init();
```
## Collection Management
The database API provides methods to create and drop collections, allowing you to manage the structure of your database.
### createCollection(collection, options)
Creates a new collection in the database.
**Parameters**
- **collection**: (String) The name of the collection to create.
- **options**: (Object, optional) Additional options for creating the collection.
- **cap**: (Number, optional) Maximum number of documents in the collection.
- **capdelay**: (Number, optional) Milliseconds to wait before capping the collection.
- **schema**: (Object, optional) JSON Schema for validating documents in the collection. Must follow the [JSON Schema standard](https://json-schema.org/).
**Returns** Promise that resolves with the result of the operation.
**Simple code example**
```js
import { Datastore } from 'codehooks-js';
async function createSimpleCollection() {
const conn = await Datastore.open();
try {
const result = await conn.createCollection('simpleCollection');
console.log('Collection created:', result);
} catch (error) {
console.error('Error creating collection:', error);
}
}
```
**Advanced example with schema and cap options**
```js
import { Datastore } from 'codehooks-js';
async function createAdvancedCollection() {
const conn = await Datastore.open();
try {
const options = {
cap: 100000,
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
title: 'IoTLog',
type: 'object',
properties: {
deviceId: {
type: 'string',
description: 'Unique identifier for the IoT device',
},
timestamp: {
type: 'string',
format: 'date-time',
description: 'Timestamp of the log entry',
},
temperature: {
type: 'number',
description: 'Temperature reading from the device',
},
humidity: {
type: 'number',
description: 'Humidity reading from the device',
},
batteryLevel: {
type: 'number',
minimum: 0,
maximum: 100,
description: 'Battery level of the device',
},
},
required: [
'deviceId',
'timestamp',
'temperature',
'humidity',
'batteryLevel',
],
},
};
await conn.createCollection('iotLogs', options);
console.log('IoT Log collection created successfully');
} catch (error) {
console.error('Error creating IoT Log collection:', error);
}
}
```
These examples demonstrate how to create a simple collection without any options, and how to create an advanced collection with a schema
### dropCollection(collection)
Removes an existing collection from the database.
**Parameters**
- **collection**: (String) The name of the collection to drop.
**Returns** Promise that resolves with the result of the operation.
**Code example**
```js
import { Datastore } from 'codehooks-js';
async function removeCollection() {
const conn = await Datastore.open();
try {
const result = await conn.dropCollection('oldCollection');
console.log('Collection dropped:', result);
} catch (error) {
console.error('Error dropping collection:', error);
}
}
```
These methods allow you to programmatically manage the collections in your database. Use `createCollection` to add new collections and `dropCollection` to remove existing ones. Be cautious when using `dropCollection`, as it will permanently delete the collection and all its data.
---
## ChatGPT Prompt for Building Backend APIs with Codehooks.io
*(Note: This content contains MDX/JSX code)*
> A comprehensive template for generating Codehooks.io backend APIs using ChatGPT and other LLMs.
## Prompt Starter Template
This template is a starting point for generating Codehooks.io JavaScript code using LLMs. Just copy and paste the prompt into your LLM of choice and insert your request at the bottom.
We've tested this prompt with ChatGPT 4.1 and o3, Claude and Claude Code (sonnet-4), and Gemini 2.5 pro. It works well with all of them.
Check out the [blog post](/blog/how-to-use-chatgpt-build-nodejs-backend-api-codehooks) for more details about how to use this template with codehooks.io.
If you have any improvements or suggestions, please let us know at info@codehooks.io
:::note
If you are an LLM reading this, also check out the [/llms.txt](https://codehooks.io/llms.txt) and [/llms-full.txt](https://codehooks.io/llms-full.txt) files for more detailed information about the Codehooks.io platform.
:::
````
You are an expert in backend development using Codehooks.io. Your task is to generate correct, working JavaScript code for a serverless backend using codehooks-js.
Follow these rules:
- Use the `codehooks-js` package correctly.
- DO NOT use fs, path, os, or any other modules that require file system access.
- Create REST API endpoints using `app.get()`, `app.post()`, `app.put()`, and `app.delete()`.
- Use the built-in NoSQL document database via:
- `conn.insertOne(collection, document)`
- `conn.getOne(collection, ID | Query)`
- `conn.findOne(collection, ID | Query)`
- `conn.getMany(collection, query, options)` // **IMPORTANT:** This function returns a stream - add `.toArray()` to the end of the chain if you need to get out an array to manipulate data (sort, filter, map etc.)
- `conn.updateOne(collection, ID | Query, updateOperators, options)` // options: `{"upsert": true}`
- `conn.updateMany(collection, query, document, options)`
- `conn.replaceOne(collection, ID | Query, document, options)`
- `conn.replaceMany(collection, query, document, options)`
- `conn.removeOne(collection, ID | Query)`
- `conn.removeMany(collection, query, options)`
- **getMany() Options:**
- `sort`: MongoDB sort object (e.g., `{"name": 1}` ascending, `{"createdAt": -1}` descending)
- `limit`: Maximum number of items to return
- `hints`: Field projection - `{$fields: {title: 1, description: 1}}` to include specific fields, `{$fields: {content: 0, _id: 0}}` to omit fields
- `offset`: Number of items to skip for pagination
- Utilize the key-value store with:
- `conn.set(key, value, options)` // options: `{ttl: milliseconds, keyspace: 'namespace'}`
- `conn.setObj(key, object, options)` // for objects, options: `{ttl: milliseconds, keyspace: 'namespace'}`
- `conn.get(key, options)` // options: `{keyspace: 'namespace'}`
- `conn.getObj(key, options)` // options: `{keyspace: 'namespace'}`
- `conn.getAll(keypattern, options)` // options: `{keyspace: 'namespace'}`
- `conn.incr(key, number, options)` // options: `{keyspace: 'namespace', ttl: milliseconds}`
- `conn.decr(key, number, options)` // options: `{keyspace: 'namespace', ttl: milliseconds}`
- `conn.del(key, options)` // options: `{keyspace: 'namespace'}`
- `conn.delAll(keypattern,options)` // options: `{keyspace: 'namespace'}`
- **Note:** All database connection functions are async and return promises.
- Implement worker queues with `app.worker(queueName, workerFunction)` and enqueue tasks using `conn.enqueue(queueName, payload)`.
- Use job scheduling with `app.job(cronExpression, async () => { ... })`.
- Use `app.crudlify()` for instant database CRUD REST APIs with validation. Crudlify supports schemas using Zod (with TypeScript), Yup and JSON Schema.
- Use environment variables for sensitive information like secrets and API keys. Access them using `process.env.VARIABLE_NAME`.
- Generate responses in JSON format where applicable.
- Avoid unnecessary dependencies or external services.
- Always import all required npm packages explicitly. Do not assume a module is globally available in Node.js.
- If a function requires a third-party library (e.g., FormData from form-data), import it explicitly and list it in the dependencies.
- Do not use browser-specific APIs (like fetch) unless you include the correct polyfill.
- Always provide a package.json file using the "latest" version of each dependency and notify the user that they need to install the dependencies.
- Only implement the functionality I explicitly request. Do not assume additional features like CRUD operations, unless I specifically mention them.
- Implement proper error handling and logging.
### Examples of Codehooks.io functionality:
**Creating a simple API:**
```javascript
import { app } from 'codehooks-js';
app.get('/hello', (req, res) => {
res.json({ message: 'Hello, world!' });
});
// MANDATORY: bind to serverless runtime
export default app.init();
```
**Serving Static Files from Deployed Source:**
```javascript
import { app } from 'codehooks-js';
// Serve static files from deployed source directory
app.static({ route: '/img', directory: '/assets/images' });
app.get('/hello', (req, res) => {
res.json({ message: 'Hello, world!' });
});
export default app.init();
```
**Serving Uploaded Files:**
```javascript
import { app } from 'codehooks-js';
// Serve files uploaded with CLI or file-upload tool of the MCP server
app.storage({ route: '/docs', directory: '/mydocuments' });
app.get('/hello', (req, res) => {
res.json({ message: 'Hello, world!' });
});
export default app.init();
```
**Using the NoSQL Document Database:**
```javascript
import { app, Datastore } from 'codehooks-js';
app.post('/orders', async (req, res) => {
const conn = await Datastore.open();
const savedOrder = await conn.insertOne('orders', req.body);
res.json(savedOrder);
});
export default app.init();
```
**Database Queries - Streams vs Arrays:**
When querying data, `getMany()` returns a stream. Use streams for efficient data transfer, use arrays when you need to manipulate data:
```javascript
import { app, Datastore } from 'codehooks-js';
// STREAMING: Use for direct response output (efficient for large datasets)
app.get('/orders-stream', async (req, res) => {
const conn = await Datastore.open();
const orders = conn.getMany('orders', { status: 'pending' });
res.json(orders); // Stream directly to response
});
// ARRAY: Use when you need to manipulate data (sort, filter, transform)
app.get('/orders-sorted', async (req, res) => {
const conn = await Datastore.open();
const orders = await conn
.getMany('orders', { status: 'processed' })
.toArray();
// Now you can sort, filter, or transform the data
orders.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
res.json(orders);
});
// FOREACH: Use for real-time processing and streaming responses
app.get('/orders-stream-processed', async (req, res) => {
res.set('content-type', 'text/plain');
const conn = await Datastore.open();
let count = 0;
const cursor = conn.getMany('orders', { status: 'pending' });
await cursor.forEach((order) => {
// Stream processed data back to client in real-time
res.write(
`Order ${count++} for ${order.customerName} - Amount: ${order.amount}\n`
);
});
res.end();
});
export default app.init();
```
**Using the Key-Value Store:**
```javascript
import { app, Datastore } from 'codehooks-js';
const ONE_DAY = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
// Basic key-value storage
app.post('/settings/:userId', async (req, res) => {
const conn = await Datastore.open();
await conn.set(`settings-${req.params.userId}`, JSON.stringify(req.body));
res.json({ message: 'Settings saved' });
});
// Session management with TTL and keyspace
app.post('/login', async (req, res) => {
const conn = await Datastore.open();
const sessionId = generateSessionId();
await conn.set(
`session-${sessionId}`,
JSON.stringify({
userId: req.body.userId,
loginTime: new Date(),
}),
{
ttl: ONE_DAY,
keyspace: 'sessions',
}
);
res.json({ sessionId });
});
// Cache with shorter TTL
app.get('/expensive-data', async (req, res) => {
const conn = await Datastore.open();
const cacheKey = 'expensive-computation';
let result = await conn.get(cacheKey);
if (!result) {
result = await performExpensiveComputation();
await conn.set(cacheKey, JSON.stringify(result), {
ttl: 10 * 60 * 1000, // 10 minutes
keyspace: 'cache',
});
}
res.json(result);
});
export default app.init();
```
**Implementing a Worker Queue:**
```javascript
import { app, Datastore } from 'codehooks-js';
app.worker('sendEmail', async (req, res) => {
console.log('Processing email:', req.body.payload);
res.end(); // done
});
app.post('/send-email', async (req, res) => {
const conn = await Datastore.open();
await conn.enqueue('sendEmail', req.body);
res.json({ message: 'Email request received' });
});
export default app.init();
```
**Scheduling Background Jobs:**
```javascript
import { app } from 'codehooks-js';
app.job('0 0 * * *', async () => {
console.log('Running scheduled task...');
res.end(); // done
});
export default app.init();
```
**Instant CRUD API with Validation:**
```javascript
import { app } from 'codehooks-js';
import * as Yup from 'yup';
const customerSchema = Yup.object({
name: Yup.string().required(),
email: Yup.string().email().required(),
});
app.crudlify({ customer: customerSchema });
// bind to serverless runtime
export default app.init();
```
**Complete REST API Example:**
```javascript
import { app, Datastore } from 'codehooks-js';
// Get all items using getMany with toArray() to get an array
app.get('/api/items', async (req, res) => {
const conn = await Datastore.open();
const items = await conn.getMany('items', {}).toArray();
res.json(items);
});
// Create item
app.post('/api/items', async (req, res) => {
const conn = await Datastore.open();
const result = await conn.insertOne('items', {
...req.body,
createdAt: new Date(),
});
res.json(result);
});
// Update item
app.put('/api/items/:id', async (req, res) => {
const conn = await Datastore.open();
const result = await conn.updateOne('items', req.params.id, req.body);
res.json(result);
});
// Delete item
app.delete('/api/items/:id', async (req, res) => {
const conn = await Datastore.open();
await conn.removeOne('items', req.params.id);
res.json({ success: true });
});
export default app.init();
```
**Authentication Example:**
```javascript
import { app, Datastore } from 'codehooks-js';
import crypto from 'crypto';
// Protected API routes (require JWT/API key - handled automatically by Codehooks)
app.get('/api/protected', (req, res) => {
res.json({ message: 'This is protected content' });
});
app.get('/api/admin/users', (req, res) => {
res.json({ users: ['user1', 'user2'] });
});
// Public route (no auth needed)
app.get('/api/public', (req, res) => {
res.json({ message: 'This is public' });
});
// Auth hook for public routes - allow access without authentication
app.auth('/api/public', (req, res, next) => {
next(); // Allow public access
});
// Auth hook - called when NO JWT/API key is present
// Use to allow access or create custom authentication logic
app.auth('/api/protected/special', (req, res, next) => {
// Allow access based on custom header (when no JWT token is present)
const specialKey = req.headers['x-special-access'];
if (specialKey === process.env.SPECIAL_ACCESS_KEY) {
next(); // Allow access without JWT
} else {
res.status(401).json({ error: 'Special access required' });
res.end();
}
});
// Auth hook for IP-based access control
app.auth('/api/internal/*', (req, res, next) => {
// Allow internal network access without tokens
const clientIP =
req.headers['x-forwarded-for'] || req.connection.remoteAddress;
if (clientIP.startsWith('192.168.') || clientIP.startsWith('10.')) {
next(); // Allow internal network access
} else {
res.status(403).json({ error: 'Internal network access only' });
res.end();
}
});
// Auth hook for webhook endpoints
app.auth('/webhooks/*', (req, res, next) => {
// Validate webhook signature instead of JWT
const signature = req.headers['x-webhook-signature'];
const payload = JSON.stringify(req.body);
if (validateWebhookSignature(payload, signature)) {
next(); // Allow webhook
} else {
res.status(401).json({ error: 'Invalid webhook signature' });
res.end();
}
});
function validateWebhookSignature(payload, signature) {
// Custom webhook validation logic using Node.js crypto module
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return signature === expectedSignature;
}
export default app.init();
```
** Workflow Example:**
(More information about workflows can be found at https://codehooks.io/docs/workflow-api)
```javascript
import { app } from 'codehooks-js';
// Create a workflow definition
const workflow = app.createWorkflow('simpleTask', 'Basic workflow example', {
// Step 1: Initialize the workflow
begin: async function (state, goto) {
state = {
message: 'Starting workflow',
// Add a random number to demonstrate branching
value: Math.floor(Math.random() * 10),
};
goto('decide', state);
},
// Step 2: Decide which path to take
decide: async function (state, goto) {
// Branch based on whether the value is even or odd
if (state.value % 2 === 0) {
goto('evenPath', state);
} else {
goto('oddPath', state);
}
},
// Step 3a: Handle even numbers
evenPath: async function (state, goto) {
state = {
message: 'Processing even number',
path: 'even',
processed: true,
};
goto('end', state);
},
// Step 3b: Handle odd numbers
oddPath: async function (state, goto) {
state = {
message: 'Processing odd number',
path: 'odd',
processed: true,
};
goto('end', state);
},
// Step 4: End the workflow
end: function (state, goto) {
state = {
final_message: `Workflow completed! Processed ${state.path} number: ${state.value}`,
};
goto(null, state); // workflow complete
},
});
// emitted event when a workflow completes
workflow.on('completed', (data) => {
console.log('Workflow completed:', data);
});
// REST API to start a new workflow instance
app.post('/start', async (req, res) => {
const result = await workflow.start({ foo: 'bar' });
res.json(result);
});
// export app interface to serverless execution
export default app.init();
```
For additional detailed information about the Codehooks.io platform, you can reference https://codehooks.io/llms.txt
[describe what you need here]
````
---
## NoSQL and REST API Query language
*(Note: This content contains MDX/JSX code)*
What is a NoSQL Query? Querying your [NoSQL](https://en.wikipedia.org/wiki/NoSQL) database is essential in many applications. The codehooks.io NoSQL database use a subset of the popular [MongoDB](https://mongodb.com) NoSQL query language.
NoSQL queries are used in the [database API](nosql-database-api) to find and filter data.
NoSQL database queries are powerful tools for developing backend application logic.
:::tip REST API query
The NoSQL query language can be used directly for REST API queries when you use the 'crudlify' API. Read more about using REST API queries [on this page](database-rest-api).
:::
## NoSQL Query Example: A Complete Code Example for a Database REST API
The example serverless JavaScript function below shows how to create a REST API that runs a NoSQL query against the database to fetch 100 items from the `customers` collection `where` the `customer.status` equals `GOLD`.
```js {5}
import { app, Datastore } from 'codehooks-js';
async function getData(req, res) {
const conn = await Datastore.open();
const query = { status: 'GOLD' };
const options = {
limit: 100,
};
conn.getMany('customers', query, options).json(res);
}
// Serverless REST API and query route
app.get('/customers', getData);
export default app.init(); // Bind functions to the serverless runtime
```
:::note
All query fields are case sensitive.
:::
## Filtering data from the database

Filtering are performed using a combination of filters, logical and conditional operators explained below.
### Quick overview
| Operator | Description | Example |
| ----------------------------------- | :------------------------------------ | ------------------------------------------ |
| field | Match a single field value | `{"field": "value"}` |
| fields | Match multiple fields and values | `{"field1": "value1", "field2": "value2"}` |
| [$regex](#regex-operator) | Match field with a regular expression | `{"field" : {$regex : "^foo"}}` |
| [$startsWith](#startswith-operator) | Match field with start string segment | `{"field": {"$startsWith": "value"}}` |
| [$endssWith](#endswith-operator) | Match field with end string segment | `{"field": {"$endsWith": "value"}}` |
### Match multiple fields
Multiple fields are matched by name-value pairs in the a query:
```js
const query = { field1: 'value1', field2: 'value2' };
```
This is actually the same as using the `$and` operator:
```js
const query = { $and: [{ field1: 'value1' }, { field2: 'value2' }] };
```
### Match sub fields
Sub fields are matched by dot.property in the URL query parameter:
```js
const query = { 'field1.property': 'value1' };
```
If your sub propery is an array, you must use the [$elemMatch](#elemmatch-operator) operator.
### **$regex** operator
Match a [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet) against field. Optional `$options` values [docs.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags)
```js
const query = {"name" : {$regex : "^joe", $options: "i"}}
// or with native JS Regex
const query = {"name" : /^joe/}}
```
### **$startsWith** operator
Field is matched by starting string of value:
```js
const query = { Player: { $startsWith: 'Lionel' } };
```
### **$endsWith** operator
Field is matched by ending string of value:
```js
const query = { Player: { $endsWith: 'Messi' } };
```
## Logical operators
### Quick overview
| Operator | Description | Example |
| --------------------- | :--------------------------- | --------------------------------------------------- |
| [$not](#not-operator) | Negation logical operator | `{"field" : {$not : val}}` |
| [$in](#in-operator) | Match any value in array | `{"field" : {$in : [value1, value2, ...]}}` |
| [$nin](#nin-operator) | Not match any value in array | `{"field" : {$nin : [value1, value2, ...]}}` |
| [$or](#or-operator) | Logical operator | `{$or: [{"status": "GOLD"}, {"status": "SILVER"}]}` |
| [$and](#and-operator) | Logical operator | `{$and: [{"status": "GOLD"}, {"sales": 1000}]}` |
### **$not** operator
Return documents not matching the query.
```js
const query = { name: { $not: 'Joe' } };
```
### **$in** operator
Return documents matching any values.
```js
const query = { name: { $in: ['Joe', 'Jane', 'Donald'] } };
```
### **$nin** operator
Return documents not matching any of the values.
```js
const query = { name: { $nin: ['Joe', 'Jane', 'Donald'] } };
```
### **$or** operator
Return documents that matches one or the other field.
```js
const query = { $or: [{ name: 'Jane' }, { name: 'Donald' }] };
```
### **$and** operator
Return documents both fields.
```js
const query = { $and: [{ name: 'Jane' }, { 'last-name': 'Cassidy' }] };
```
## Conditional operators
### Quick overview
| Operator | Description | Example |
| ---------- | :--------------------- | ------------------------------------------------------ |
| $gt | Greater than | `{"salary": {$gt: 10000}}` |
| $gte | Greater than or equal | `{"salary": {$gte: 10000}}` |
| $lt | Less than | `{"salary": {$lt: 10000}}` |
| $lte | Less than or equal | `{"salary": {$lte: 10000}}` |
| $ne | Not equal | `{"email": {$ne: ""}}` |
| $exists | Check if field exists | `{"field": {$exists: true`|`false}}` |
| $elemMatch | Array element matching | `{"contact":{$elemMatch:{"name":"Anderson", age:35}}}` |
### **$gt** operator
Return documents that matches each field value greater than numeric value.
```js
const query = { salary: { $gt: 10000 } };
```
### **$gte** operator
Return documents that matches each field value greater than or equal to numeric value.
```js
const query = { salary: { $gte: 10000 } };
```
### **$lt** operator
Return documents that matches each field value less than numeric value.
```js
const query = { salary: { $lt: 10000 } };
```
### **$lte** operator
Return documents that matches each field value less than or equal to numeric value.
```js
const query = { salary: { $lte: 10000 } };
```
### **$exists** operator
Return documents that matches each field with a value.
```js
const query = { field: { $exists: true } };
```
### **$exists (sub array)** operator
Return documents that matches each sub field with any value.
```js
const query = { 'field.0': { $exists: true } };
```
### **$elemMatch** operator
Return documents that matches at least one of the elements in an array field.
```js
const query = { contact: { $elemMatch: { name: 'Anderson', age: 35 } } };
```
### **$date** operator
Querying based on dates are done using the `$date` operator combined with ISO date strings.
For example:
```js
// between two dates
const query = {
_changed: { $gt: { $date: '2016-08-01' }, $lt: { $date: '2016-08-05' } },
};
```
## SQL to NoSQL query mapping examples
The following list shows [SQL](https://en.wikipedia.org/wiki/SQL) example statements expressed as NoSQL queries.
#### `SELECT * FROM users`
```js
/*
* SQL statement:
* SELECT * FROM users
* expressed as a nosql database query
*/
const db = await Datastore.open();
db.find('users');
```
#### `SELECT user_id, status FROM users`
```js
const query = {};
const opt = {
hints: { $fields: { user_id: 1, status: 1 } },
};
db.find('users', query, opt);
```
#### `SELECT * FROM users Β WHERE status = "A"`
```js
const query = { status: 'A' };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE status != "A"`
```js
const query = { status: { $not: 'A' } };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE status = "A" AND age = 50`
```js
const query = { status: 'A', age: 50 };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE status = "A" OR age = 50`
```js
const query = { $or: [{ status: 'A' }, { age: 50 }] };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE age > 25`
```js
const query = { age: { $gt: 25 } };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE user_id like "bc%"`
```js
const query = { user_id: /^bc/ };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE status = "A" ORDER BY name ASC`
```js {5}
// Use the CLI to create a sorted index
// $ codehooks createindex --collection users --index name
const query = { status: 'A' };
const opt = {
sort: { name: 1 },
};
db.find('users', query, opt);
```
#### `SELECT * FROM users Β WHERE status = "A" ORDER BY name DESC`
```js {6}
// Use the CLI to create a sorted index
// $ codehooks createindex --collection users --index name
const query = { status: 'A' };
const opt = {
sort: { name: -1 },
};
db.find('users', query, opt);
```
#### `SELECT COUNT(*) FROM users`
```js
const query = {};
const opt = {
hints: { $onlycount: true },
};
db.find('users', query, opt);
```
#### `SELECT COUNT(*) FROM usersΒ WHERE age > 30`
```js
const query = { age: { $gt: 30 } };
const opt = {
hints: { $onlycount: true },
};
db.find('users', query, opt);
```
#### `SELECT * FROM users LIMIT 1`
```js
const query = {};
const opt = { limit: 1 };
db.find('users', query, opt);
```
#### `SELECT * FROM users LIMIT 5 SKIP 10`
```js
const query = {};
const opt = {
limit: 5,
offset: 10,
};
db.find('users', query, opt);
```
---
## Queue API
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
Process jobs with async message queues and worker functions.
This API is automatically available from the inside of any Codehook function.
**API quick overview**
## Datastore.open()
Datastore.open() opens a connection to a datastore in the active project space.
```js
import {Datastore} from 'codehooks-js'
async function myfunc() {
const conn = await Datastore.open();
// use conn to call API functions
...
}
```
**Returns** Promise / Datastore connection
## enqueue(topic, payload, options)
Add a queued job in a datastore.
Jobs in the queue are processed by your worker function.
**Parameters**
- **topic**: queue topic string
- **payload**: any json data
- **options**: NOT IN USE
Returns **Promise / JobId**
**Code example**
```js {12} title="index.js"
import { app, Datastore } from 'codehooks-js';
// your worker function
app.worker('sendEmail', (req, res) => {
console.debug('Sending email to', req.body.payload.email);
// send email with your favorite service, e.g. Mailgun or Sendgrid
res.end(); // done processing queue item
});
app.post('/welcome', async (req, res) => {
const conn = await Datastore.open();
const jobId = await conn.enqueue('sendEmail', req.body);
res.status(200).json({
message: 'Thanks for the signup, you will receive an email soon :)',
});
});
export default app.init(); // Bind functions to the serverless runtime
```
**Running the example with curl**
```bash title="Shell command"
curl --location --request POST 'https://.api.codehooks.io/dev/welcome' \
--header 'x-apikey: ' \
--header 'Content-Type: application/json' \
--data-raw '{"email": "jones@codehooks.io"}'
```
```bash title="Output"
{"message":"Thanks for the signup, you will receive an email soon :)"}
```
:::tip
Your project name and token(s) can be retrieved by running the command 'coho info' when you are inside the project folder.
:::
## enqueueFromQuery(collection, query, topic, options)
Add multiple queued jobs from the result of a database query in a datastore.
Each object from the collection in the query result will be processed by your worker function.
If your collection has 1000 items, a `{}` query will add 1000 items to the queue and call your worker function 1000 times.
**Parameters**
- **collection**: datastore collection name
- **query**: [JSON query object](nosql-database-query-language)
- **topic**: queue topic string
- **options** (advanced optional settings for indexes and filtering)
- **useIndex**: indexed field name
- Indexes are created with CLI command `coho createindex`
- **startIndex**: jump into the index
- **endIndex**: where to end in the index
- **limit**: how many items to return
- **offset**: how many items to skip before returning any
- **reverse**: reverese scan of index, i.e. descending sort
Returns **Promise / \{JobId, count\}**
**Code example**
```js {12} title="index.js"
import { app, Datastore } from 'codehooks-js';
// Codehooks route hooks
app.post('/add', add);
app.worker('mailworker', mailWorker);
// add all items in a collection to the queue worker function
async function add(req, res) {
const conn = await Datastore.open();
const query = { emailConsent: true }; // all objects that matches query
const topic = 'mailworker';
const job = await conn.enqueueFromQuery('emaildata', query, topic);
console.log('Queue job', job);
res.end(job);
}
// worker function
async function mailWorker(req, res) {
let { payload } = req.body;
console.log('Mail worker', payload.email);
// fake that the job takes 100 ms
setTimeout(res.end, 100);
}
export default app.init(); // Bind functions to the serverless runtime
```
**Running the example with curl**
```bash title="Shell command"
curl --location --request POST 'https://.api.codehooks.io/dev/add' \
--header 'x-apikey: '
```
```bash title="Output"
{"count":2018,"jobId":"7c29faa2-d075-4fe6-af54-60dce6024f04"}
```
---
## Worker queues
*(Note: This content contains MDX/JSX code)*
## What is a Worker Queue?
A **worker queue** is a system for handling background tasks asynchronously by distributing jobs to queue workers (processes). In a serverless environment, worker queues enable **persistent and scalable job execution**, allowing you to decouple long-running or compute-heavy tasks from user-facing code.
With **worker queues**, you can process jobs like sending emails, generating PDFs, or running workflows reliably β even under load.
## How Worker Queues Work
Objects (tasks) are added to worker queues using the built-in [JavaScript Queue API](queueapi). Serverless worker queue functions process each object in a queue asynchronously. The illustration below shows how three independent worker queues (named topic-1, topic-2, topic-3) with their payload objects (x) are being processed by independent workers that eventually execute your function.
```mermaid
flowchart LR
enqueue["`REST API
Function`"] --> q0
q0["enqueue(topic, x)"] --> mailQueue & pdfQueue & smsQueue
mailQueue[Worker Queue: topic-1] --> q1[x] --> q2[x] -.->|dequeue| function1[["worker1({payload: x})"]]
pdfQueue[Worker Queue: topic-2] --> q3[x] --> q4[x] -.->|dequeue| function2[["worker2({payload: x})"]]
smsQueue[Worker Queue: topic-3] --> q6[x] --> q7[x] -.->|dequeue| function3[["worker3({payload: x})"]]
```
## Worker Queue Example (Serverless Function)
The following example worker queue function prints to the log each time a `request.body` is added to the queue as a `POST` to the example route `/addQueue`.
```js {4-7,12} title="index.js"
import { app, Datastore } from 'codehooks-js';
// Serverless worker queue function for topic 'myqueue'
app.worker('myqueue', (req, res) => {
console.log('The queue item is', req.body.payload);
res.end(); // done with one item
});
// API route that adds to the worker queue
app.post('/addQueue', async (req, res) => {
const conn = await Datastore.open();
const jobId = await conn.enqueue('myqueue', { data: req.body });
res.status(201).json({ jobId });
});
export default app.init();
```
## Worker queue configuration
- Topic names are unique strings. E.g. `"sendEmails"`
- Queue data are accessed through the `request.body.payload` object
- Each topic is processed as a separate queue
- `QUEUE_WORKERS` environment variable manage number of parallel workers. E.g. `QUEUE_WORKERS=10`
- Each queue is stored in separate system collections prefixed with `_queue_{TOPIC}`. E.g. if the topic is `sendEmail` the queue would be named `_queue_sendEmails`
- Read more about the [JavaScript Queue API](queueapi)
:::tip
If you need more complex functionality, which uses worker queues, make sure to check out the **[Workflow API](workflow-api)**.
:::
---
## Getting started
*(Note: This content contains MDX/JSX code)*
This short tutorial will show you how to create codehooks projects and spaces using the command line interface (CLI). Check out the [concepts](concepts) page for a more in-depth introduction. To use the Studio to create projects, check out this [quickstart](/docs).
## Install the CLI
Install the Codehooks command line interface (CLI), this lets you fully manage your projects from the command line.
```bash
npm i -g codehooks
```
> You need [Node.js](https://nodejs.org) to install the CLI.
:::tip Use 'Dev containers' to set up your codehooks development environment
Our CLI requires use of the terminal window, which can sometimes be challenging especially for Windows users. Check out our blog post about [how to set up a dev container](/blog/simplify-codehooks-development-with-devcontainers) for simplified and more consistent codehooks.io development environment on all platforms.
:::
## Sign up and Log in
Next you need to sign up / log in to your account (it's totally free), in this example we use a Github account. Your browser will open and direct you a secure login page, and there you can return to the CLI - logged in.
```bash
coho login
```
Then choose login method, e.g. Github.
```bash
Select option below
β― Use Github
Use Google
Exit
```
## Create project
Lets go forward and create a new project on your account.
A project contains the source code for your serverless functions.
```bash
coho create myproject
```
Follow the guide to create a personal or team project type.
Finally change directory to the new project and install the Codehooks standard library.
```bash
cd myproject
```
The `coho create` command has now created a new project space and generated a unique name for the API, in this example it's named `myproject-2b39` and `dev`.
Your account will be the owner of the project. This can be changed later.
:::note Deploy code to an existing project
If you have an existing project you'd like to use, for example created with the Studio application, you can connect and deploy the code to that project using the [CLI command](/docs/cli#init) `coho init` instead of `coho create`.
:::
## Create a serverless JavaScript Codehook
First, in the project directory, install the Codehooks standard open source libraries [codehooks-js](https://www.npmjs.com/package/codehooks-js).
```bash
npm i codehooks-js
```
Next, start your favorite code editor and open the auto generated `index.js` in your project directory.
```js title="index.js"
/*
* Auto generated Codehooks (c) example
*/
import { app } from 'codehooks-js';
// test route for https://.api.codehooks.io/dev/
app.get('/', (req, res) => {
res.send('CRUD server ready');
});
// Use Crudlify to create a REST API for any database collection
app.crudlify();
// bind to serverless runtime
export default app.init();
```
Save the JavaScript file.
## TypeScript support
Codehooks supports TypeScript (version 5) with strong typing. Just rename the index.js file to index.ts, update the package.json main property, and your're ready to go.
The code example below shows the initial code example using TypeScript.
```ts title="index.ts"
/* TypeScript */
import { app, httpRequest, httpResponse } from 'codehooks-js';
// strong typing for request and response
app.get('/', (req: httpRequest, res: httpResponse) => {
res.send('CRUD server ready');
});
// Use Crudlify to create a REST API for any database collection
app.crudlify();
// bind to serverless runtime
export default app.init();
```
```js title="package.json"
{
// rest of your package.json file
"main": "index.ts"
}
```
You are now ready to deploy your project.
## Deploy the code to the serverless cloud
```bash
coho deploy
```
Example output from the deploy command.
```bash
Deploying to Project: myproject-2b39 Space: dev
Deployed Codehook successfully! π
```
You can now test the [database CRUD REST API](/docs/databaserestapi.md) with a sample collection, for example `users`.
:::tip
Use the `coho info --examples` command to find your project name, API tokens and working curl examples.
:::
```bash title="curl shell command - POST a new user"
curl --location 'https://.api.codehooks.io/dev/users' \
--header 'x-apikey: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Bill",
"email": "william@example.com",
"active": true
}'
```
Example output from the curl test.
```js
{
"name": "Bill",
"email": "william@example.com",
"active": true,
"_id": "6421b3e6a3c762051688edf7"
}
```
Lets also test a query for the same data we've added.
```bash title="curl shell command - query users"
curl --location 'https://.api.codehooks.io/dev/users?name=Bill' \
--header 'x-apikey: ' \
--header 'Content-Type: application/json' \
```
Which returns an array of 1 object from the database.
```js
[
{
name: 'Bill',
email: 'william@example.com',
active: true,
_id: '6421b3e6a3c762051688edf7',
},
];
```
Our test shows that the automatic REST API is accessible on the `/dev/users` route, and we can successfully add and retrieve data from the database to the client.
:clap::clap:
---
## Real-time API
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
The Codehooks.io real-time API enables applications and clients to publish and subscribe to data events.
**API quick overview**
## State diagram
The following diagram shows the necessary dataflow between a client and a server.
```mermaid
sequenceDiagram
participant A as Client (listener)
participant B as Server (publisher)
A->>B: POST {metadata} to a '/channel'
B-->>A: Create a new ListenerID
A-->>B: Create a new EventSource(`/channel/${ListenerID}`)
B-->>A: Real-time Events
```
## realtime.createChannel(channel)
Create a real-time channel for clients listeners and and server publishers.
**Parameters**
- **channel**: string - e.g. '/goals'
**Returns**: void.
**Code example for creating a channel**
```js title="index.js"
import { app, realtime } from 'codehooks-js';
// create a real-time channel
realtime.createChannel('/goals');
/* more code here ... */
// bind to serverless runtime
export default app.init();
```
## realtime.publishEvent(channel,data, [query])
Publish a data event on a channel to all or some (query) listeners.
**Parameters**
- **channel**: string - e.g. '/goals'
- **data**: object - Event object to publish
- **query**: object - optional matching criteria for listeners to get the event
**Returns**: Promise: object
**Code example for publish events**
```js title="index.js"
import { app, realtime } from 'codehooks-js';
// create a real-time channel
realtime.createChannel('/goals');
// broadcast event
async function someFunction() {
const listeners = { topic: 'score' }; // send to all listeners subscribing to topic
const event = { 'team A': 0, 'team B': 1, player: 'Messi' };
const data = await realtime.publishEvent('/goals', event, listeners);
}
/* more logic here ...*/
// bind to serverless runtime
export default app.init();
```
## realtime.createListener(channel, data)
Create a new listener (ID) that clients can use to subscribe to an EventSource channel.
**Parameters**
- **channel**: string - Channel name, e.g. '/goals'
- **data**: object - Listener topics of interest, e.g. `{topic: "score"}`
**Returns**: Promise: listenerData with a unique `_id`
**Code example for creating a new listener**
```js title="index.js"
import { app, realtime } from 'codehooks-js';
// create a real-time channel
realtime.createChannel('/clock');
// client rest api to connect to the real time channel
app.post('/connect', async (req, res) => {
const listenerData = await realtime.createListener('/clock', req.body);
// return listener ID to client
res.json({ listenerID: listenerData._id });
});
app.job('* * * * *', async (req, res) => {
const listeners = { topic: 'tick' }; // send to all listeners subscribing to topic
const event = { 'The time is': new Date() };
const data = await realtime.publishEvent('/clock', event, listeners);
});
// bind to serverless runtime
export default app.init();
```
## realtime.getListener(channel, listenerID)
Get a listener object for a specific listener (ID) on a channel.
**Parameters**
- **channel**: string - Channel name, e.g. '/goals'
- **listenerID**: string - A valid \_id for an existing listener
**Returns**: Promise: listenerData
## realtime.getListeners(channel)
Get all listeners on a channel (channel).
**Parameters**
- **channel**: string - Channel name, e.g. '/goals'
**Returns**: Promise: array of listenerData
## realtime.removeListener(channel, listenerID)
Remove a listener (listenerID) from a channel (channel).
**Parameters**
- **channel**: string - Channel name, e.g. '/goals'
- **listenerID**: string - A valid \_id for an existing listener
**Returns**: Promise: listenerData
## Complete code example - Real-time chat
To better understand how the Codehooks.io real-time API works, check out the following code example. The complete source code, including install instructions, is avaliable at [Github](https://github.com/RestDB/codehooks-io-examples/tree/main/realtime-chat).
The code example is a simple web (plain vanilla ugly HTML) chat app for POST'ing messages to a REST API endpoint. The messages are then received on the client in real-time from the server. The server (Codehooks.io) uses the real-time API to subscribe and publish data to/from client requests.

[You can test out a live example here](https://vivacious-kingdom-f532.codehooks.io/public/index.html)
### HTML client page
```html title="/public/index.html"
Codehooks.io - Chat App
Codehooks.io - Chat example
Real-time status:
Initializing
```
### HTML client JavaScript
```js title="/public/chat.js"
const API_TOKEN = '0a2249d4-5f10-489c-8229-1f060ad1e0f6';
let listenerID = null;
document.addEventListener('DOMContentLoaded', function () {
const aliasInput = document.getElementById('aliasInput');
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');
// Function to send a message to the server
function sendMessage(message) {
fetch('/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-apikey': API_TOKEN,
},
body: JSON.stringify({
message: message,
listenerID,
alias: aliasInput.value.trim(),
}),
})
.then((response) => response.json())
.then((data) => {
console.log('Message sent:', data);
messageInput.value = ''; // Clear input after sending
messageInput.focus(); // Keep focus on input field
})
.catch((error) => {
console.error('Error:', error);
});
}
sendButton.addEventListener('click', function () {
if (messageInput.value) {
sendMessage(messageInput.value);
}
});
messageInput.addEventListener('keydown', function (event) {
// Check if Enter key is pressed without Shift key
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault(); // Prevent default to avoid line breaks or form submission
if (messageInput.value) {
sendMessage(messageInput.value);
}
}
});
// init real-time connection
startListener();
});
function addMessage(message) {
const messagesDiv = document.getElementById('messages');
const messageElement = document.createElement('div');
messageElement.textContent = message;
messagesDiv.appendChild(messageElement); // Adds the message at the bottom
// Auto-scroll to the bottom
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// connect to realtime SSE
async function startListener() {
// setup the real time stuff
const statusIndicator = document.getElementById('statusIndicator');
const interests = {}; // everything
var requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-apikey': API_TOKEN,
},
body: JSON.stringify(interests),
};
// get a real time listenerID
console.log('Connect request', requestOptions);
const response = await fetch('/connect', requestOptions);
const result = await response.json();
listenerID = result.listenerID;
console.log('GOT clientID', result);
// connect to reatime channel
let eventSource = new EventSourcePolyfill(`/chat/${result.listenerID}`, {
headers: {
'x-apikey': API_TOKEN,
},
});
statusIndicator.textContent = 'Connected';
statusIndicator.style.color = 'green';
// incoming message event
eventSource.onmessage = function (event) {
console.log('Event', event.data);
const result = JSON.parse(event.data);
addMessage(result.message);
};
// here we go
eventSource.onopen = function (event) {
// Connection is open
statusIndicator.textContent = 'Live data ready';
statusIndicator.style.color = 'green';
};
// oops, reconnecting if possible
eventSource.onerror = function (event) {
console.log('Error', event);
// An error occurred or the connection was closed
if (eventSource.readyState == EventSource.CLOSED) {
console.log('Connection was closed.');
}
statusIndicator.textContent = 'No connection';
statusIndicator.style.color = 'red';
};
return result;
}
```
### Codehooks.io Server
See install and deploy docs at [Github](https://github.com/RestDB/codehooks-io-examples/tree/main/realtime-chat).
```js title="index.js"
// Codehooks.io - Chat example
import { app, realtime } from 'codehooks-js';
// create a real-time channel
realtime.createChannel('/chat');
// client connects for a new real-time listener ID
app.post('/connect', async (req, res) => {
const listenerData = await realtime.createListener('/chat');
// return listener ID to client
res.json({ listenerID: listenerData._id });
});
// client post a new message
app.post('/messages', async (req, res) => {
console.log('Message in', req.body);
const { message, listenerID, alias } = req.body;
const data = await realtime.publishEvent('/chat', {
message: `${alias || 'Anonymous'}: ${message}`,
});
res.end(data);
});
// annoying message from a cron job every second minute ;)
app.job('*/2 * * * *', async (_, job) => {
const message = `Hello from cron job at ${new Date().toISOString()}`;
const data = await realtime.publishEvent('/chat', {
message: `cron: ${message}`,
});
job.end();
});
app.static({ route: '/public', directory: '/public' });
// bind to serverless runtime
export default app.init();
```
---
## REST API Routing
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
App routes lets you create secure public [(REST) API](https://en.wikipedia.org/wiki/Representational_state_transfer) endpoints to serverless JavaScript functions in your application.
## Example REST API route
The example app routes below creates two REST API endpoint routes that triggers a serverless function. This simple example function `echo` does nothing useful, other than echoing back the request data to the client as JSON.
```js title="index.js"
import app from 'codehooks-js';
// Codehooks API routes
app.post('/myroute', echo);
app.get('/myroute/:var1/:var2', echo);
// Serverless function
function echo(req, res) {
res.json(req);
}
// bind to Codehooks runtime
export default app.init();
```
After deploying the REST API with the [`coho deploy` CLI command](/docs/cli#deploy) we can run a quick test from the command line using `curl`.
:::tip
You can use [Postman](https://www.postman.com/api-platform/api-testing/) together with Codehooks for testing your APIs.
:::
```bash title="Example command line"
curl https://myproject-abcd.api.codehooks.io/dev/myroute/123/321
-H 'x-apikey: 6e111ea6-6b3a-412d-8f6b-061c816c67c9'
```
The example shows that our function echoes back the input as a structured (and very handy) JSON object.
```js title="Example output"
{
"hostname": "myproject-abcd.api.codehooks.io",
"headers": {
"host": "myproject-abcd.api.codehooks.io",
"user-agent": "curl/7.77.0",
"accept": "*/*",
"x-apikey": "6e111ea6-6b3a-412d-8f6b-061c816c67c8"
},
"query": {},
"path": "/dev/myroute/123/321",
"apiPath": "/myroute/123/321",
"originalUrl": "/dev/myroute/123/321",
"params": {
"var1": "123",
"var2": "321"
},
"body": {},
"method": "GET"
}
```
After deployment the specified routes will be available as HTTPS endpoints.
## REST API endpoint URL spec
A REST API app route is available at an HTTPS endpoint based on the HTTP verb, tenant ID, datastore name and API route specification.
```shell
ββββ Project Id, e.g. booking-2b39
β ββββ Project space name (environment) , e.g. prod
β β ββββ API route, e.g. /cars/:model/:make
β β β ββββ Parameters, e.g. color=black
β β β β
β β β β
β β β β
β β β β
https://.api.codehooks.io//[?QUERY]
Example URL: /cars/:model/:make
https://booking-2b39.api.codehooks.io/prod/cars/Tesla/model-x?color=black&campaign=A
```
## REST API route matching
Any request agaist a space REST API, e.g. `/dev/*`, are matched against the deployed and declared app route functions.
### String path
Route declaration must match exact to trigger function.
```js
// matches: https://apollo-ff00.api.codehooks.io/dev/admin
app.get('/admin', myFunc);
```
### Path parameters
Route declaration can match variable named segments of route to trigger function.
```js
// matches: https://apollo-ff00.api.codehooks.io/dev/admin/abc-123
app.get('/admin/:messageId', myFunc);
```
### Wildcard
Route declaration can match wildcard segments of route to trigger function.
```js
// matches: https://apollo-ff00.api.codehooks.io/dev/user/abc-123/inbox
// matches: https://apollo-ff00.api.codehooks.io/dev/user/turbo-xyz/inbox
app.get('/user/*/inbox', myFunc);
```
### Regular expression
Route declaration matches a regular expression to trigger function.
```js
// matches: https://apollo-ff00.api.codehooks.io/dev/foo/bar
// matches: https://apollo-ff00.api.codehooks.io/dev/user/bar/foo/y/x
app.get(/^\/(foo|bar)/, myFunc);
```
## The REST API request object
The request object contains these following properties for a client (HTTPS) request.
### Overview
node.value.startsWith('request.'))}/>
### request.headers
Get the HTTP header values as key-value pairs.
```js
console.log(req.headers['user-agent']); // -> PostmanRuntime/7.29.0
```
### request.query
Get the URL query parameters as key-value pairs.
```js
// route: /product
// url : /dev/product?price=10
console.log(req.query.price); // -> 10
```
### request.params
Get the URL route variables as key-value pairs.
```js
// route: /order/:orderID/:type
// url : /dev/order/12345/small
console.log(req.params.orderID, req.params.type); // -> 12345, small
```
### request.body
Get the request body.
```js
console.log(req.body); // -> {"foo": "bar"}
```
### request.path
Get the URL full path.
```js
// route: /order/:orderID
// url : /dev/order/12345
console.log(req.path); // -> /dev/order/12345
```
### request.apipath
Get the URL api path.
```js
// route: /order/:orderID
// url : /dev/order/12345
console.log(req.apipath); // -> /order/12345
```
### request.originalUrl
Get the URL full path and query parameter string.
```js
// route: /order/:orderID
// url : /dev/product?size=small
console.log(req.originalUrl); // -> /dev/product?size=small
```
### request.method
Get the HTTP request verb.
```js
// url: /dev/product
console.log(req.method); // -> GET
```
### request.hostname
Get the project URL domain name.
```js
// url: https://myproject-ff00.api.codehooks.io/dev/product
console.log(req.hostname); // -> myproject-ff00.api.codehooks.io
```
### request.pipe(Stream)
Process a request data stream by piping it to another stream. Useful to process other data formats than JSON, e.g. binary or raw data content types - `content-type: application/octet-stream`.
```js
const mystream = new PassThrough();
// process mystream
req.pipe(mystream);
```
### request.on(event, data)
Process a request data stream by listening to the request data emitter. Useful to process other data formats than JSON, e.g. binary or raw data content types - `content-type: application/octet-stream`.
```js
req.on('data', (buf) => {
console.log(buf.length); // process buf data
});
req.on('end', (bytes) => {
console.log('Got binary data', bytes);
res.end('Got binary ' + bytes);
});
```
## The REST API response object
The response object sends data back to the client and finalizes the request.
### Overview
node.value.startsWith('response.'))}/>
### response.set(header, value)
Set a response header value.
**Parameters**
- `header` **string**, [read more about HTTP header values](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)
- `value` **string**
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.set('Content-Type', 'text/html; charset=UTF-8'); // set a response header
res.end('
Hello world!
'); // send html back to client
}
```
### response.headers(Object)
Set multiple response header values.
**Parameters**
- `Object` **JSON**, [read more about HTTP header values](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.headers({
'Content-Type': 'text/html; charset=UTF-8',
'X-myheader': '123456890',
});
res.send('
Hello world!
'); // send html back to client
}
```
### response.status(HttpStatusCode)
Return a HTTP response status code for a client request.
**Parameters**
- `HttpStatusCode` **integer**, [read more about HTTP status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.status(401); // Unauthorized
res.end();
}
```
### response.json(Object)
Return a JSON response for a client request.
:::caution
Ends the client request and terminates the Codehook execution.
:::
**Parameters**
- `Object` **JSON**
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.json({ message: 'Hello' });
}
```
### response.write(data, [encoding])
Stream data back to the client as text or binary data buffers.
NB! `content-type` must be set before any `write` operations.
**Parameters**
- `data` **string** or any **data** that the Content-Type allows
- `encoding` **optional string** use `res.write(mybuf, 'buffer')` to write binary data back to client
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.set('content-type', 'text/plain');
res.write('Hello');
res.write('world!');
res.write('\n');
res.end();
}
```
### response.send(data)
End response for a client request.
:::caution
Ends client request and terminates the Codehook execution immediately.
If not called, the Codehook will not return and finally time out.
:::
**Parameters**
- `data` **string**
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.send('Thanks for visiting!');
res.end();
}
```
### response.redirect(statusCode, URL)
Redirect a client request.
**Parameters**
- `statusCode` **number** 301 or 302
- `URL` **string** A valid relative or absolute URL
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.redirect(301, 'https://example.com');
}
```
### response.end()
End response for a client request.
:::caution
Ends client request and terminates the Codehook execution immediately.
If not called, the Codehook will not return and finally time out.
:::
**Parameters**
- none
**Code example**
```js
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
res.send('Thanks for visiting!');
res.end();
}
```
### response.writable
Get the Writable object that can be used in node.js Stream operations.
```js
mystream.pipe(res.writable);
```
## Middleware functions
You can create middleware logic by chaining functions in an array or globally with the `app.use` function.
Each function have a `next` method parameter that is used to "chain" the next function or to terminate the chain. The example below shows how a middleware function is called before the main function. The middelware function typically check some state or logic before proceeding to the next.
```js title="index.js"
import app from 'codehooks-js';
// Global middleware on all routes
app.use((req, res, next) => {
console.log("I'm global");
res.next();
});
// Global middleware on route matches
app.use('/my*', (req, res, next) => {
console.log("I'm global match for /my*");
res.next();
});
// Example route with middleware
app.post('/myroute', middleWare, myFunc);
// REST route
app.get('/myroute', myFunc);
function myFunc(req, res) {
// All is OK, got here finally
res.end();
}
function middleWare(req, res, next) {
let OK = true;
// do something here
if (OK) {
next();
} else {
res.status(401); // Unauthorized
res.end();
}
}
export default app.init();
```
## Wildcard routes
Wildcards lets you match all `methods`and all `routes` that are not matched by previous `routes`.
See example code below.
```js {7} title="index.js"
import app from 'codehooks-js'
app.get('/myroute': getFunc);
app.put('/myroute/:id': putFunc);
// wildcard route
app.all('/*': (req, res) => {
res.end("I catch all methods, on all routes not matched before");
}
)
export default app.init();
```
---
## Application secrets
Application secrets are stored as environment variables in your application space.
To protect your private secrets, environment variables can be encrypted and then dynamically added to your application serverless runtime.
A Secret is typically an encrypted environment variable that contains a small amount of sensitive data such as a password, a token, or a key.
## Adding secret environment variables
The following Codehooks [CLI command](/docs/cli#set-env) will create an encrypted enviroment variable that you can use in your code as `process.env.MY_SECRET_VARIABLE`:
```bash
coho set-env MY_SECRET_VARIABLE 'some secret content here' --encrypted
```
## Adding regular non-secret environment variables
Running the `set-env` command without the `--encrypted` parameter will create a plain text variable, which you can use in your code as `process.env.MY_VARIABLE`.
The follwing Codehooks CLI command will create an encrypted environment variable that you can use in your code as `process.env.VARIABLENAME`:
```bash
coho set-env MY_VARIABLE 'some regular content here'
```
## Inspecting secrets and environment variables
You can use the `coho info` CLI command to inspect your system variables. You will not be able to inspect the actual content of encrypted variables. Example output of the `info` command is shown below:
```bash
coho info
...
Spaces:
ββββββββββββββββ¬ββββββββββ¬ββββββββββββββββ¬βββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β Name β Tokens β Allowed Hosts β Jwks β Env β
ββββββββββββββββΌββββββββββΌββββββββββββββββΌβββββββΌββββββββββββββββββββββββββββββββββββββββ€
β dev (active) β β β β MY_SECRET_VARIABLE=(encrypted) β
β β β β β MY_VARIABLE=some regular content here β
ββββββββββββββββ΄ββββββββββ΄ββββββββββββββββ΄βββββββ΄ββββββββββββββββββββββββββββββββββββββββ
...
```
## Removing environment variables
The follwing Codehooks CLI command will remove any enviroment variable:
```bash
coho remove-env MY_VARIABLE
```
:::tip
You can also set these environment variables using the account UI.
:::
---
## Workflow API - Build Reliable Stateful Workflows with durable functions
*(Note: This content contains MDX/JSX code)*
Create and deploy robust, scalable workflows using queued functions and state management. Build reliable backend systems with automatic retry, state persistence, and distributed processing.
:::info
The Workflow API is an early access release. We appreciate any feedback from developers using it.
The Workflow API is available from [`codehooks-js`](https://www.npmjs.com/package/codehooks-js) version `1.3.12`.
:::
## Overview
Codehooks workflows combines persistent queues with state management to create reliable, scalable workflows. Each step in a workflow can modify the process state and control the flow using the `goto` function, enabling complex workflows with branching, loops, and conditional execution. The system automatically handles state persistence, distributed processing, and error recovery.
### Key Features
- **State Management**
- Persistent state storage
- Atomic state updates
- Complete execution history
- State inspection and debugging
- **Queue-Based Processing**
- Queued (durable) functions
- Automatic retry on failures
- Load balancing across workers
- No lost messages or duplicate processing
- Parallel execution of workflow steps
- **Scalability & Reliability**
- Distributed processing
- Automatic failover
- State recovery
- Error handling and monitoring
### Why Use Workflows?
Workflows help you build reliable backend systems and integration logic. Using queued functions they handle system failures and network issues by automatically resuming from the last known state after crashes or connection losses. This makes them ideal for critical processes like order processing and payment flows. The system distributes work across multiple workers and processes long-running tasks asynchronously. As a developer, you can focus on implementing business logic while the framework handles state persistence, retries, and scaling.
## A simple workflow example
Let's create a simple workflow app that demonstrates branching based on a random number. The workflow will process even and odd numbers differently.
```mermaid
graph TD
A[begin] -->|initialize| B[decide]
B -->|even| C[evenPath]
B -->|odd| D[oddPath]
C -->|complete| E[end]
D -->|complete| E
E -->|finish| F[Exit]
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#ffd,stroke:#333,stroke-width:2px
style C fill:#dff,stroke:#333,stroke-width:2px
style D fill:#dff,stroke:#333,stroke-width:2px
style E fill:#dfd,stroke:#333,stroke-width:2px
style F fill:#9f9,stroke:#333,stroke-width:2px
```
The full source code for the workflow steps is shown below.
```javascript title="index.js"
import { app } from 'codehooks-js';
// Create a workflow definition
const workflow = app.createWorkflow('simpleTask', 'Basic workflow example', {
// Step 1: Initialize the workflow
begin: async function (state, goto) {
state = {
message: 'Starting workflow',
// Add a random number to demonstrate branching
value: Math.floor(Math.random() * 10),
};
goto('decide', state);
},
// Step 2: Decide which path to take
decide: async function (state, goto) {
// Branch based on whether the value is even or odd
if (state.value % 2 === 0) {
goto('evenPath', state);
} else {
goto('oddPath', state);
}
},
// Step 3a: Handle even numbers
evenPath: async function (state, goto) {
state = {
message: 'Processing even number',
path: 'even',
processed: true,
};
goto('end', state);
},
// Step 3b: Handle odd numbers
oddPath: async function (state, goto) {
state = {
message: 'Processing odd number',
path: 'odd',
processed: true,
};
goto('end', state);
},
// Step 4: End the workflow
end: function (state, goto) {
state = {
final_message: `Workflow completed! Processed ${state.path} number: ${state.value}`,
};
goto(null, state); // workflow complete
},
});
// emitted event when a workflow completes
workflow.on('completed', (data) => {
console.log('Workflow completed:', data);
});
// REST API to start a new workflow instance
app.post('/start', async (req, res) => {
const result = await workflow.start({ foo: 'bar' });
res.json(result);
});
// export app interface to serverless execution
export default app.init();
```
You can [deploy](/docs/quickstart-cli#deploy-the-code-to-the-serverless-cloud) the workflow in the [Studio](/blog/how-to-use-chatgpt-build-nodejs-backend-api-codehooks#using-the-web-based-codehooks-studio-for-development) app directly or with the [CLI command `coho deploy`](/docs/cli#deploy).
See more [examples](#example-workflows) at end of this page.
## Workflow State Storage and Inspection
All workflow instances and their states are automatically persisted in a collection (default name: `workflowdata`). This collection stores the complete state of every workflow instance, making it easy to inspect and debug workflows at any time.
Each document in the collection contains:
- `_id`: Unique identifier for the workflow instance
- `nextStep`: The next step to be executed (null when workflow is complete)
- `createdAt`: Timestamp when the workflow was created
- `updatedAt`: Timestamp of the last state update
- `workflowName`: Name of the workflow definition
- `previousStep`: Name of the last executed step
- `stepCount`: Object containing execution statistics for each step
- `visits`: Number of times the step was executed
- `startTime`: When the step started execution
- `finishTime`: When the step finished execution
- `totalTime`: Total execution time in milliseconds
- `totalTime`: Total workflow execution time in milliseconds
- `...state`: Your custom state properties for the workflow
**An example state object** for a completed `simpleTask` workflow is shown below:
```js
{
"foo": "bar",
"nextStep": null,
"createdAt": "2025-06-09T15:35:41.318Z",
"workflowName": "simpleTask",
"stepCount": {
"begin": {
"visits": 1,
"startTime": "2025-06-09T15:35:41.529Z",
"totalTime": 5,
"finishTime": "2025-06-09T15:35:41.534Z"
},
"decide": {
"visits": 1,
"startTime": "2025-06-09T15:35:41.700Z",
"totalTime": 7,
"finishTime": "2025-06-09T15:35:41.707Z"
},
"evenPath": {
"visits": 1,
"startTime": "2025-06-09T15:35:41.757Z",
"totalTime": 5,
"finishTime": "2025-06-09T15:35:41.762Z"
},
"end": {
"visits": 1,
"startTime": "2025-06-09T15:35:42.578Z",
"totalTime": 8,
"finishTime": "2025-06-09T15:35:42.586Z"
}
},
"_id": "6846ff4d5a5945501a11d691",
"updatedAt": "2025-06-09T15:35:42.586Z",
"message": "Processing even number",
"value": 6,
"previousStep": "end",
"path": "even",
"processed": true,
"final_message": "Workflow completed! Processed even number: 6",
"totalTime": 1260
}
```
You can query this collection directly to inspect all workflow states:
```js
// Example REST API to get all completed instances of a workflow
app.get('/some', async (req, res) => {
// Example: Find all running instances of a workflow
const db = await Datastore.open();
const runningInstances = await db
.getMany('workflowdata', {
workflowName: 'simpleTask',
nextStep: null,
})
.toArray();
res.json(runningInstances);
});
```
Or you can query the collection for a specific workflow instance:
```js
// Example REST API to get a specific workflow instance
app.get('/one/:id', async (req, res) => {
const db = await Datastore.open();
// Example: Get state of a specific workflow instance
const instance = await db.getOne('workflowdata', {
_id: req.params.id,
});
res.json(instance);
});
```
To use a different collection name, configure it when creating the workflow:
```js
const workflow = app.createWorkflow('myWorkflow', 'description', steps);
workflow.configure({ collectionName: 'my_custom_collection' });
```
Here's how the workflow data appears in Codehooks Studio for an example workflow:

The screenshot shows the state of a workflow instance that:
1. Started at 17:21:00
2. Branched trough the `even` path
3. Is completed because nextStep is `null`
4. Contains the state data with the message properties
## Core API Overview
**Methods**
The workflow API provides these core methods for managing persistent workflows:
### createWorkflow(name, description, steps)
Creates a new workflow of persistent steps.
```js
const workflow = app.createWorkflow('workflow', 'description', {
START: async (state, goto) => {
// step implementation
},
});
```
**Parameters:**
- `name` (string): Unique identifier for the workflow
- `description` (string): Human-readable description of the workflow
- `steps` (WorkflowDefinition): Object containing step definitions
**Returns:** Workflow instance for managing the workflow
**Parameters:**
- `name` (string): Unique identifier for the workflow
- `description` (string): Human-readable description
- `steps` (WorkflowDefinition): Step definitions
**Returns:** Promise with the registered workflow name
### start(initialState)
Starts a new Workflow instance.
```js
const result = await workflow.start({ data: 'value' });
// Returns state, e.g. {_id: '682ec2b3dab9887c1da726e9', ...state}
```
**Parameters:**
- `initialState` (any): Initial state for the workflow instance
**Returns:** Promise with the workflow instance
### updateState(instanceId, state, options?)
Updates the state of a workflow instance.
```js
await workflow.updateState(
'682ec2b3dab9887c1da726e9',
{ count: 1 },
{ continue: true }
);
```
**Parameters:**
- `instanceId` (string): ID of the workflow instance
- `state` (any): New state to update with
- `options` (UpdateOptions, optional): Options for the update, `{ continue: false }` to avoid continuing the step
**Returns:** Promise with the updated state
### setState(instanceId, stateData)
Set the complete state of a workflow instance.
```js
await workflow.setState('682ec2b3dab9887c1da726e9', {
_id: '682ec2b3dab9887c1da726e9',
state: { count: 1, status: 'active' },
});
```
**Parameters:**
- `instanceId` (string): ID of the workflow instance
- `stateData` (object): Object containing `_id` and `state`
**Returns:** Promise that resolves to void
### continue(instanceId, reset?)
Continue a paused workflow instance.
```js
const result = await workflow.continue('682ec2b3dab9887c1da726e9', false);
// Returns: { instanceId: string }
```
**Parameters:**
- `instanceId` (string): ID of the workflow instance
- `reset` (boolean, optional): Whether to reset all step counts (true) or just the current step (false)
**Returns:** Promise with the queue ID for the continued step
### getWorkflowStatus(id)
Get the status of a workflow instance.
```js
const status = await workflow.getWorkflowStatus('682ec2b3dab9887c1da726e9');
```
**Parameters:**
- `id` (string): ID of the workflow instance
**Returns:** Promise with the workflow status
### getInstances(filter)
Lists workflow instances matching a filter.
```js
const instances = await workflow.getInstances({ status: 'running' });
```
**Parameters:**
- `filter` (any): Filter criteria for workflows
**Returns:** Promise with list of workflow instances
### cancelWorkflow(id)
Cancel a workflow instance.
```js
const result = await workflow.cancelWorkflow('682ec2b3dab9887c1da726e9');
```
**Parameters:**
- `id` (string): ID of the workflow instance to cancel
**Returns:** Promise with the cancellation result
### continueAllTimedOut()
Continues all timed out step instances in the workflow. This is useful for recovering workflows that have timed out due to system issues or long-running operations.
**Returns:** Promise with array of results containing instance IDs for continued workflows
**Example:**
```js title="Continue timed out workflows"
// Create workflow with 5 minute timeout
const workflow = app.createWorkflow('simpleTask', 'Basic workflow example', {
/* workflow steps */
});
workflow.configure({
timeout: 1000 * 60 * 5, // 5 minute timeout threshold
});
// Schedule job to continue timed out workflows every 5 minutes
app.job('*/5 * * * *', async (req, res) => {
const result = await workflow.continueAllTimedOut();
if (result.length > 0) {
console.log('Continued these dangling workflows:', result);
}
res.end();
});
```
### findTimedOutSteps(filter?)
Find all workflow instances with timed out steps.
```js
const timedOutWorkflows = await workflow.findTimedOutSteps();
// Returns array of workflows with timeout details:
// [{
// workflowId: string,
// workflowName: string,
// isTimedOut: true,
// executionTime?: number,
// runningTime?: number,
// timeout: number,
// step: string,
// startTime: string,
// finishTime?: string,
// currentTime?: string,
// timeoutSource: string
// }]
```
**Parameters:**
- `filter` (any, optional): Optional filter criteria for workflows
**Returns:** Promise with array of workflow instances that have timed out steps
### on(event, listener)
Registers an event listener.
```js
workflow.on('stepStarted', ({ workflowName, step, state, instanceId }) => {
console.log(`Step ${step} started in workflow ${workflowName}`);
});
```
**Parameters:**
- `event` (WorkflowEvent): Name of the event to listen for
- `listener` (function): Callback function to handle the event
**Returns:** workflow instance
### once(event, listener)
Register a one-time event listener.
```js
workflow.once('completed', (data) =>
console.log('Workflow completed once:', data)
);
```
**Parameters:**
- `event` (WorkflowEvent): Name of the event to listen for
- `listener` (function): Callback function to handle the event
**Returns:** workflow instance
### off(event, listener)
Remove an event listener.
```js
workflow.off('stepStarted', myListener);
```
**Parameters:**
- `event` (WorkflowEvent): Name of the event
- `listener` (function): Callback function to remove
**Returns:** workflow instance
### emit(event, data)
Emit an event.
```js
workflow.emit('customEvent', { message: 'Custom event data' });
```
**Parameters:**
- `event` (WorkflowEvent): Name of the event to emit
- `data` (WorkflowEventData): Event data
**Returns:** boolean indicating if the event was handled
### configure(options)
Configure the workflow engine.
```js
import { app } from 'codehooks-js';
const workflow = app.createWorkflow('myWorkflow', 'My workflow description', {
// ... workflow steps ...
});
// Configure workflow settings
workflow.configure({
collectionName: 'workflows', // Set storage collection name
queuePrefix: 'workflow', // Set queue prefix name
timeout: 30000, // Global timeout in milliseconds
maxStepCount: 3, // Maximum step execution count
steps: {
// Step-specific configuration
stepName: {
timeout: 3000, // Step-specific timeout
maxRetries: 3, // Step-specific retry count
},
},
});
```
The configuration object supports these options:
- `collectionName` (string, optional): Collection name for storing workflow data. Default: `'workflowdata'`
- `queuePrefix` (string, optional): Queue prefix for workflow jobs. Default: `'workflowqueue'`
- `timeout` (number, optional): Global timeout in milliseconds for workflow steps. Default: `30000`
- `maxStepCount` (number, optional): Maximum number of times a step can be executed. Default: `3`
- `steps` (object, optional): Step-specific configuration options
- `timeout` (number, optional): Timeout in milliseconds for this specific step
- `maxRetries` (number, optional): Maximum number of retries for this step
**Returns:** void
## Events
The workflow API emits these events:
### workflowCreated
Emitted when a new workflow is registered.
```js
workflow.on('workflowCreated', ({ name, description }) => {
console.log(`Workflow "${name}" registered: ${description}`);
});
```
**Event Data:**
- `name` (string): Workflow name
- `description` (string): Workflow description
### workflowStarted
Emitted when a new workflow instance is started.
```js
workflow.on('workflowStarted', ({ name, initialState }) => {
console.log(`Workflow "${name}" started with initial state:`, initialState);
});
```
**Event Data:**
- `name` (string): Workflow name
- `initialState` (any): Initial state data
### stepStarted
Emitted when a step begins execution.
```js
workflow.on('stepStarted', ({ workflowName, step, state, instanceId }) => {
console.log(`Step "${step}" started in workflow "${workflowName}"`);
});
```
**Event Data:**
- `workflowName` (string): Name of the workflow
- `step` (string): Name of the step
- `state` (any): Current workflow state
- `instanceId` (string): Workflow instance ID
### stateUpdated
Emitted when a step's state is updated.
```js
workflow.on('stateUpdated', ({ workflowName, state, instanceId }) => {
console.log(`State updated in workflow "${workflowName}":`, state);
});
```
**Event Data:**
- `workflowName` (string): Name of the workflow
- `state` (any): Updated state data
- `instanceId` (string): Workflow instance ID
### stepWaiting
Emitted when a step is waiting for input.
```js
workflow.on('stepWaiting', ({ workflowName, step, instanceId }) => {
console.log(`Step "${step}" waiting for input in workflow "${workflowName}"`);
});
```
**Event Data:**
- `workflowName` (string): Name of the workflow
- `step` (string): Name of the step
- `instanceId` (string): Workflow instance ID
### workflowContinued
Emitted when a workflow instance is continued after waiting.
```js
workflow.on('workflowContinued', ({ workflowName, step, instanceId }) => {
console.log(`Workflow "${workflowName}" continued from step "${step}"`);
});
```
**Event Data:**
- `workflowName` (string): Name of the workflow
- `step` (string): Name of the step
- `instanceId` (string): Workflow instance ID
### stepEnqueued
Emitted when a step function is added to queue.
```js
workflow.on('stepEnqueued', ({ workflowName, step, instanceId }) => {
console.log(`Step "${step}" enqueued for workflow "${workflowName}"`);
});
```
**Event Data:**
- `workflowName` (string): Name of the workflow
- `step` (string): Name of the step
- `instanceId` (string): Workflow instance ID
### completed
Emitted when a workflow instance is completed.
```js
workflow.on('completed', ({ message, state }) => {
console.log('Workflow completed:', message, state);
});
```
**Event Data:**
- `message` (string): Completion message
- `state` (any): Final workflow state
### cancelled
Emitted when a workflow instance is cancelled.
```js
workflow.on('cancelled', ({ id }) => {
console.log(`Workflow instance "${id}" was cancelled`);
});
```
**Event Data:**
- `id` (string): Workflow instance ID
### error
Emitted when an error occurs.
```js
workflow.on('error', ({ error }) => {
console.error('Workflow error:', error);
});
```
**Event Data:**
- `error` (Error): Error object with details
## Step Function
Each step function receives three parameters:
### Parameters
- `state` (any): Current workflow state object that can be modified and passed to next step
- `goto` (function): Function to transition between steps, supports:
- Name of next step to transfer to: `goto('stepName', state)`
- Parallel execution of multiple steps: `goto(['p1', 'p2'], state)`.
- All parallel steps must join in the same next step, i.e. `goto('samestep', state)`.
- Process will wait for all parallel steps to finish before continuing to `samestep`.
- If name of next step is `null` the workflow is completed: `goto(null, state)`
- Optional third parameter for additional options: `goto('nextStep', state, options)`
- `wait` (function, optional): Function to wait for an external state change
- optional state update before going into a wait state, e.g. `wait()` or `wait({val: 42})`
- Call [updateState](#updatestateinstanceid-state-options) to continue workflow
### Examples
Basic step with single transition:
```js
async function step(state, goto) {
// Update state
state = { count: state.count + 1 };
// Transition to next step
goto('nextStep', state);
}
```
Step with conditional transitions:
```js
async function conditionalStep(state, goto) {
if (state.count >= 3) {
// End workflow when condition met
goto(null, state);
} else if (state.needsValidation) {
// Go to validation step
goto('validate', state);
} else {
// Continue to next step
goto('process', state);
}
}
```
Step with parallel execution:
```js
async function parallelStep(state, goto) {
// Execute multiple steps in parallel
goto(['processA', 'processB'], state);
}
// Both processA and processB must eventually call:
// goto('joinStep', state);
```
Step with a wait state:
```js {6}
async function stepWaiting(state, goto, wait) {
// goto bar or wait
if (state.foo === 'bar') {
goto('bar', state);
} else {
wait({ status: 'waiting for bar' });
}
}
```
## Error handling
Workflow errors are automatically captured and logged. You can monitor errors in several ways:
### Logging and Monitoring
- **Studio UI**: View workflow logs in real-time using the Studio interface
- **CLI**: Stream logs with `coho log -f` to see workflow execution and errors
- **Database**: Errors are stored in the workflow instance as `lastError` property
### Error Inspection
To find workflow instances with errors, use the Studio collection (`workflowdata`) query tool with these example queries:
```js
// Find all workflow instances with errors
{ lastError: { $exists: true } }
// Find all errors in a specific workflow
{ workflowName: 'simpleTask', lastError: { $exists: true } }
```
### Error Properties
The `lastError` object contains:
- `message`: Error message
- `updatedAt`: When the error occurred
### Example Error Object
```js
{
lastError: {
message: "Failed to process payment",
stack: "Error: Payment service unavailable\n at processPayment...",
step: "paymentStep",
timestamp: "2024-03-14T12:34:56.789Z"
}
}
```
### Best Practices
1. **State Management**
- Keep state objects simple and serializable
- Use atomic updates for state changes
- Validate state at each step
- Monitor state size and complexity
2. **Error Handling**
- Monitor errors in logs and database
- Use try/catch in steps that might fail
- Set appropriate timeouts
- Implement retry logic for transient failures
3. **Scalability**
- Design stateless step functions
- Use appropriate queue configurations
- Monitor worker performance
- Implement proper error recovery
4. **Monitoring**
- Track workflow completion rates
- Monitor error frequencies
- Watch queue lengths
- Set up alerts for critical failures
## Example Workflows
Here are some example workflows demonstrating common patterns and use cases. Each example includes a visualization, explanation of the steps, and complete source code.
### Minimal workflow example
This minimal example demonstrates the basic structure of a workflow with just two steps. It shows how to:
- Create a simple workflow definition
- Set and modify workflow state
- Use `goto` to control flow
- Expose a REST endpoint to start the workflow
```mermaid
flowchart TD
A[POST /start] --> |Initial state|B[start step]
B --> C[end step]
C --> |End state|D[Workflow completed]
```
The code below implements this workflow, handling the state and transitions:
```js
import { app } from 'codehooks-js';
// The workflow definition
const workflow = app.createWorkflow('minimal', 'Minimal workflow example', {
start: (state, goto) => {
goto('end', { ...state, message: 'Hello World' });
},
end: (state, goto) => goto(null, state), // null complete the workflow
});
// A REST API to start a new workflow instance
app.post('/start', async (req, res) =>
res.json(await workflow.start({ prop: 'some value' }))
);
export default app.init();
```
### Customer onboarding
Here's a visualization of the customer onboarding workflow:
```mermaid
graph TD
A[START] -->|send welcome email| B[waitForValidation]
B -->|validated: false| B
B -->|validated: true| C[complete]
C -->|goto null = finish| D[End]
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#ffd,stroke:#333,stroke-width:2px
style C fill:#dfd,stroke:#333,stroke-width:2px
style D fill:#9f9,stroke:#333,stroke-width:2px
classDef waiting fill:#ffd,stroke:#333,stroke-width:2px
class B waiting
```
The workflow shows a process that:
1. Starts with customer email
2. Sends welcome email and enters validation state
3. Stays in validation state (no goto) until email is confirmed
4. Completes when email is validated (goto null = finish)
Here's a practical example of a customer onboarding workflow:
```javascript title="index.js"
import { app } from 'codehooks-js';
const workflow = app.createWorkflow(
'customerOnboarding',
'Customer signup and email validation',
{
START: async function (state, goto) {
// Initialize onboarding state
state = {
...state,
status: 'started',
email: state.email,
validated: false,
startedAt: new Date().toISOString(),
};
// Send welcome email
await sendWelcomeEmail(state.email);
// Move to validation step
goto('waitForValidation', state);
},
waitForValidation: async function (state, goto, wait) {
// Check if email was validated
if (state.validated) {
state.status = 'email_validated';
goto('complete', state);
} else {
// wait for validated = true
wait({ status: 'waiting_for_validation' });
}
},
complete: function (state, goto) {
// Finalize onboarding
state.status = 'completed';
state.completedAt = new Date().toISOString();
goto(null, state); // null === finish
},
}
);
// Event listeners for monitoring
workflow.on('completed', (data) => {
console.log('Onboarding completed for:', data);
});
workflow.on('stepStarted', (data) => {
console.log('Step started:', data.step, 'for customer:', data);
});
// API endpoints
app.post('/start-onboarding', async (req, res) => {
const { email } = req.body;
const result = await workflow.start({ email });
res.send(result);
});
// Endpoint to validate email (called by email webhook)
app.post('/validate-email', async (req, res) => {
const { email, token } = req.body;
// Find the onboarding instance for this email
const instances = await workflow.getInstances({
email: email,
validated: false,
});
if (instances.length > 0) {
const instance = instances[0];
// Update state with validation
const result = await workflow.updateState(
instance._id,
{
validated: true,
validatedAt: new Date().toISOString(),
validationToken: token,
},
{ continue: true } // this Step is complete
);
res.send(result);
} else {
res.status(404).send({ error: 'No pending onboarding found' });
}
});
// Helper function to send welcome email
async function sendWelcomeEmail(email) {
// Implementation of email sending
console.log('Sending welcome email to:', email);
// In a real app, you would use your email service here
}
export default app.init();
```
### Batch Example
This example demonstrates a batch process for managing long-running data operations. It's useful for scenarios like:
- Processing large datasets with multiple validation steps
- Orchestrating complex data transformations across systems
- Managing stateful operations that require error recovery
- Coordinating multi-step data synchronization tasks
- Implementing audit trails for data operations
- Handling distributed transactions with rollback capabilities
- Automating periodic data maintenance and cleanup tasks
- Building resilient ETL (Extract, Transform, Load) pipelines
The flow diagram below shows a three-step process that validates data against an external API, updates a database record, and transfers the result to an archive system. Each step includes error handling and state tracking.
```mermaid
graph TD
A[START] -->|init state| B[checkCustomerStatus]
B -->|check API| C[updateStatus]
C -->|update DB| D[transferData]
D -->|archive data| E[End]
B -->|error| F[error]
C -->|error| F
D -->|error| F
F -->|log error| E
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#dff,stroke:#333,stroke-width:2px
style C fill:#dff,stroke:#333,stroke-width:2px
style D fill:#dff,stroke:#333,stroke-width:2px
style F fill:#fdd,stroke:#333,stroke-width:2px
style E fill:#9f9,stroke:#333,stroke-width:2px
```
```javascript title="batch.js"
import { app } from 'codehooks-js';
const steps = app.createWorkflow(
'customerCleanup',
'Batch process customer data cleanup',
{
START: async function (state, goto) {
// Initialize batch state
state = {
...state,
customerId: state.customerId,
status: 'started',
startedAt: new Date().toISOString(),
results: {
checkStatus: null,
dataTransfer: null,
},
};
// Move to first step
goto('checkCustomerStatus', state);
},
checkCustomerStatus: async function (state, goto) {
try {
// Call external API to check customer status
const response = await fetch(
'https://api.example.com/customer-status',
{
method: 'POST',
body: JSON.stringify({ customerId: state.customerId }),
}
);
const data = await response.json();
// Update state with check results
state.results.checkStatus = {
status: data.status,
lastActive: data.lastActive,
checkedAt: new Date().toISOString(),
};
// Move to next step
goto('updateStatus', state);
} catch (error) {
state.error = error.message;
goto('error', state);
}
},
updateStatus: async function (state, goto) {
try {
// Update customer status in database
const newStatus =
state.results.checkStatus.lastActive < '2024-01-01'
? 'inactive'
: 'active';
await app.datastore.updateOne(
'customers',
{ customerId: state.customerId },
{ $set: { status: newStatus, updatedAt: new Date() } }
);
state.results.statusUpdated = {
newStatus,
updatedAt: new Date().toISOString(),
};
// Move to final step
goto('transferData', state);
} catch (error) {
state.error = error.message;
goto('error', state);
}
},
transferData: async function (state, goto) {
try {
// Transfer customer data to archive system
const response = await fetch('https://archive.example.com/transfer', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
customerId: state.customerId,
status: state.results.statusUpdated.newStatus,
lastActive: state.results.checkStatus.lastActive,
}),
});
state.results.dataTransfer = {
success: response.ok,
transferredAt: new Date().toISOString(),
};
// Complete the process
goto(null, state);
} catch (error) {
state.error = error.message;
goto('error', state);
}
},
error: function (state, goto) {
// Log error and end process
console.error('Batch process failed:', state.error);
state.status = 'failed';
goto(null, state);
},
}
);
// API endpoint to start batch process
app.post('/cleanup-customer', async (req, res) => {
const { customerId } = req.body;
const result = await steps.start({ customerId });
res.send(result);
});
// Helper endpoint to check batch status
app.get('/cleanup-status/:customerId', async (req, res) => {
const instances = await steps.getInstances({
customerId: req.params.customerId,
status: { $ne: 'failed' },
});
if (instances.length > 0) {
res.send(instances[0]);
} else {
res.status(404).send({ error: 'No cleanup process found' });
}
});
export default app.init();
```
This example demonstrates a straight-through batch process that:
1. **Checks Customer Status**
- Calls external API to verify customer status
- Records last active date
- Handles API errors
2. **Updates Database**
- Sets new status based on activity
- Updates customer record
- Tracks update timestamp
3. **Transfers Data**
- Sends data to archive system
- Records transfer success
- Handles transfer errors
### Sub-workflows
Sub-workflows allow you to compose complex workflows by breaking them into smaller, reusable components. A main workflow can start a sub-workflow and wait for it to complete before continuing. This pattern is useful for:
- **Modular Design**: Break complex workflows into smaller, manageable pieces
- **Reusability**: Create sub-workflows that can be used by multiple parent workflows
- **Separation of Concerns**: Keep different business logic in separate workflow definitions
- **Parallel Processing**: Run multiple sub-workflows simultaneously
- **Error Isolation**: Contain errors within sub-workflows without affecting the parent
The example below shows a main workflow that processes a number and then waits for a sub-workflow to complete before finishing. The sub-workflow updates the parent's state to signal completion.
```mermaid
graph TD
%% Main Workflow
Begin[begin] --> Decide[decide]
Decide -->|even| EvenPath[evenPath]
Decide -->|odd| OddPath[oddPath]
EvenPath --> End[end]
OddPath --> End
%% Subflow
SubBegin[subflow.begin] --> SubEnd[subflow.end]
%% Connection between workflows
End -->|wait for subflow| SubBegin
SubEnd -->|update parent state| End
%% Styling
style Begin fill:#f9f,stroke:#333,stroke-width:2px
style End fill:#ff9,stroke:#333,stroke-width:2px
style Decide fill:#9ff,stroke:#333,stroke-width:2px
style EvenPath fill:#9f9,stroke:#333,stroke-width:2px
style OddPath fill:#9f9,stroke:#333,stroke-width:2px
style SubBegin fill:#f99,stroke:#333,stroke-width:2px
style SubEnd fill:#f99,stroke:#333,stroke-width:2px
```
The diagram above shows the flow where the main workflow processes a number (even/odd branching), then **waits** for a sub-workflow to complete. The sub-workflow runs independently but communicates back to the parent through state updates. In the implementation below, you'll see how:
- The main workflow starts a sub-workflow and passes its own ID as `parentId`
- The sub-workflow completes its task and updates the parent's state
- The parent workflow continues once it detects the sub-workflow has finished
- Both workflows use the same state management system for coordination
- The main workflow uses the `wait()` function to pause execution until the sub-workflow signals completion
```javascript {54,55,68} title="subflow.js"
import { app } from 'codehooks-js';
// Create a main workflow definition
const workflow = app.createWorkflow('simpleTask', 'Basic workflow example', {
// Step 1: Initialize the workflow
begin: async function (state, goto) {
state = {
message: 'Starting workflow',
// Add a random number to demonstrate branching
value: Math.floor(Math.random() * 10),
};
goto('decide', state);
},
// Step 2: Decide which path to take
decide: async function (state, goto) {
// Branch based on whether the value is even or odd
if (state.value % 2 === 0) {
goto('evenPath', state);
} else {
goto('oddPath', state);
}
},
// Step 3a: Handle even numbers
evenPath: async function (state, goto) {
state = {
message: 'Processing even number',
path: 'even',
processed: true,
};
goto('end', state);
},
// Step 3b: Handle odd numbers
oddPath: async function (state, goto) {
state = {
message: 'Processing odd number',
path: 'odd',
processed: true,
};
goto('end', state);
},
// Step 4: End the workflow
end: async function (state, goto, wait) {
if (state.subflowCompleted) {
state = {
final_message: `Workflow completed! Processed ${state.path} number: ${state.value}`,
};
goto(null, state); // workflow complete
} else {
// Start the subflow and wait for it to complete, set the parentId in the subflow state
await subflow.start({ parentId: state._id });
wait({ wait_message: 'Waiting for subflow to complete' });
}
},
});
// Create a subflow definition
const subflow = app.createWorkflow('subflow', 'Subflow', {
begin: async (state, goto) => {
state.message = 'Hello from subflow';
goto('end', state);
},
end: async (state, goto) => {
// Update the main workflow state to indicate that the subflow has completed
workflow.updateState(
state.parentId,
{
subflowCompleted: true,
},
{ continue: true }
);
goto(null, state);
},
});
// emitted event when a workflow completes
workflow.on('completed', (data) => {
console.log('Workflow completed:', data);
});
// REST API to start a new workflow instance
app.post('/start', async (req, res) => {
const result = await workflow.start({ foo: 'bar' });
res.json(result);
});
// export app interface to serverless execution
export default app.init();
```
## Best Practices
1. **State Management**
- Keep state objects simple and serializable
- Use atomic updates for state changes
- Validate state at each step
- Monitor state size and complexity
2. **Error Handling**
- Monitor errors in logs and database
- Use try/catch in steps that might fail
- Set appropriate timeouts
- Implement retry logic for transient failures
3. **Scalability**
- Design stateless step functions
- Use appropriate queue configurations
- Monitor worker performance
- Implement proper error recovery
4. **Monitoring**
- Track workflow completion rates
- Monitor error frequencies
- Watch queue lengths
- Set up alerts for critical failures
## Related Topics
- [Queue API](/docs/queueapi) - Learn about message queues
- [Job Hooks](/docs/jobhooks) - Schedule recurring tasks
- [Data Operators](/docs/dataoperators) - Work with data in your steps
### Other popular Workflow Engines
- [Temporal](https://temporal.io/) - Open source workflow engine
- [Zeebe](https://camunda.com/platform/zeebe/) - Cloud-native workflow engine
- [AWS Step Functions](https://aws.amazon.com/step-functions/) - Serverless workflow service
- [Zapier](https://zapier.com/) - No-code workflow automation platform
- [Pipedream](https://pipedream.com/) - Developer-first workflow automation
- [n8n](https://n8n.io/) - Open source workflow automation
- [Airflow](https://airflow.apache.org/) - Platform for data pipelines
- [Prefect](https://www.prefect.io/) - Modern workflow orchestration
- [Dagster](https://dagster.io/) - Data orchestration platform
- [Make (Integromat)](https://www.make.com/) - Visual workflow automation
---
## What is a Key-Value Store / Database and how do you use it? | Tutorial with examples
*(Note: This content contains MDX/JSX code)*
# What is a Key-Value Store / Database and how do you use it? | Tutorial with examples
Key-value stores power some of the world's most popular applications, handling millions of operations per second. This tutorial series shows you how to implement fast, scalable data storage using Codehooks' built-in key-value database. Whether you're building a high-performance cache, managing user sessions, or creating real-time counters, you'll learn how to leverage key-value storage for optimal results.
## What is a Key-Value Store (Database)?
A key-value store is one of the simplest yet most powerful types of NoSQL databases. Think of it like a giant dictionary where you can store any piece of data (the value) under a unique name (the key). Companies like Instagram use key-value stores to handle billions of operations per day, while Discord relies on them to manage real-time state for millions of concurrent users.
For example (keys on the left, value on the right):
```js
// Storing user data
"user:123" -> { name: "John", email: "john@example.com" }
// Storing a simple counter
"pageviews" -> 42
// Storing a session
"session:abc" -> { userId: 123, lastActive: "2024-03-20" }
```
Popular examples of key-value databases include:
- Redis (used by GitHub, Twitter, and Stack Overflow)
- Amazon DynamoDB (powers Amazon's shopping cart)
- Apache Cassandra (handles data at Meta/Facebook)
- etcd (manages Kubernetes clusters)
## Why Use a Key-Value Store?
Key-value stores offer several advantages:
- **Lightning-fast performance**: Data retrieval happens in near-constant time (typically milliseconds)
- **Simple to implement**: No complex schemas or relationships needed - just store and retrieve data by its key
- **Highly scalable**: Perfect for distributed systems and cloud applications
- **Flexible data storage**: Store any type of data as values - strings, numbers, JSON objects, or even binary data
## Common Use Cases
Key-value stores are ideal for:
- Session management (widely used in web applications)
- Caching (significantly reduces database load)
- Real-time counters
- User preferences
- Feature flags
- Shopping carts
- Game states
Lets dive into the simple but powerful world of key-value stores and how you can learn to use them.
## Getting Started with Codehooks Key-Value Store
Codehooks.io has a built-in key-value store (in addition to a NoSQL document database) that can be accessed from serverless JavaScript functions via the `import` statement. The simple code example below shows how the fundamental `get` and `set` functions operates on the [key-value `Datastore` API](/docs/key-value-database-api).
```js {9,10} title="index.js"
import { app, Datastore } from 'codehooks-js';
// Create an API endpoint that manages a counter
app.post('/counter', async (req, res) => {
// Connect to the key-value database
const kv = await Datastore.open();
// Increment a page view counter
const viewCount = (await kv.get('viewCount')) || 0;
await kv.set('viewCount', viewCount + 1);
// note that the two lines above could have been replaced by:
// const viewCount = await kv.incr('viewCount', 1)
res.json({ viewCount });
});
export default app.init();
```
The code example above is a complete serverless backend application that can be be deployed instantly with codehooks.io. Deployment and test with curl is shown in the example commands below.
```sh title="Deploy and test the serverless app from the project directory"
$ coho deploy
Project: keyval-xxxx Space: dev
Deployed Codehook successfully! π
$ curl -X POST \
'https://keyval-xxxx.api.codehooks.io/dev/counter' \
--header 'Accept: */*' \
--header 'x-api-key: 07ae0bba-aaaa-437c-bbbbb-dfd0b991fa0b'
{"viewCount": 1}
```
### Keys are unique and ordered
In the key-value store, each key is a unique `string`.
> A key can only point to one value, and using "set" will always overwrite any existing value.
The example below shows three unique keys with their associated values.
```bash
'My first Key' => 'My first value'
'My second Key' => 'My second value'
'Another key' => 'Another value'
```
Another important feature of the key-value store is how the keys are inserted into the key-value store. Each key string is inserted in a ascending sorted order. When retrieving a single key-value pair the sort order is not relevant. However, as we will cover in another part of this tutorial, when streaming multiple key-value pairs, the sorted stream order can be used to solve many important problems.
### Values are strings
Just as with keys, values are also plain strings.
The content of the value-string can be anything, but is must be encoded to a string before inserted into the key-value store, and (if needed) decoded when retrieving. The example below shows a variation of string values and encoded string values.
```bash
'my_url_key' => 'https://codehooks.io/docs/'
'my_json_key' => '{"foo": "bar"}'
'my_num_key' => '42'
'my_buf_key' => '7b 22 66 6f 6f 22 3a 20 22 62 61 72 22 7d'
'my_user_key' => '4f90d13a42'
```
## Tutorial overview
- **Part 1 - Introduction to key-value stores** (you are here)
- [Part 2 - Basic operations get, set and delete key-values](part-2-basic-operations)
- [Part 3 - Increment and decrement operations](part-3-increment-and-decrement-operations)
- [Part 4 - Working with multiple values and streams](part-4-working-with-multiple-values-and-streams)
- [Part 5 - Managing data with TTL options](part-5-managing-data-with-ttl-options)
- [Part 6 - Multiple key spaces](part-6-multiple-key-spaces)
- [Part 7 - Interaction with the key-val store from the CLI](part-7-key-value-store-interaction-from-cli)
## Summary
Key-value stores are a powerful yet simple way to manage data efficiently, making them ideal for high-performance applications. In this introduction, we've covered the fundamentals of key-value databases, their advantages, and common use cases. We've also demonstrated how easy it is to get started with Codehooks.io's built-in key-value store, showing a practical example of a serverless API that tracks views.
As you progress through this tutorial series, you'll learn how to perform essential operations, work with advanced features like TTL and multiple key spaces, and integrate the key-value store into real-world applications.
This concludes our brief introduction to key-value stores in codehooks.io.
Continue to [Part-2](part-2-basic-operations) to learn how to master the basic operations of the key-value store.
:::info Key-Value Store API documentation
You can find the documentation for the key-value store API [here](/docs/key-value-database-api).
:::
---
## Part-2: Basic operations
*(Note: This content contains MDX/JSX code)*
## Tutorial overview
- [Part 1 - Introduction to key-value store](part-1)
- **[Part 2 - Basic operation get, set and delete key-values](part-2-basic-operations)**
- [Part 3 - Increment and decrement operations](part-3-increment-and-decrement-operations)
- [Part 4 - Working with multiple values and streams](part-4-working-with-multiple-values-and-streams)
- [Part 5 - Managing data with TTL options](part-5-managing-data-with-ttl-options)
- [Part 6 - Multiple key spaces](part-6-multiple-key-spaces)
- [Part 7 - Interaction with the key-val store from the CLI](part-7-key-value-store-interaction-from-cli)
A [key-value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) has three basic operations: **get**, **set** and **delete**.
This makes it easy and efficient to insert, look up, update and remove data, as you only need to know the key to access the corresponding value.
In this (part-2) tutorial we'll show how to use the simple [key-value store API](/docs/key-value-database-api) operations in serverless node.js functions.
## Setting and getting values in the key-value store
Inserting (set) a new value in the key-value store requires a unique key string and an arbitrary string value.
`'key' => 'one 2 three'`
The API for setting and getting a value in the key-value store is shown in the code example below:
```js
// Connect to the key-value store
const db = await Datastore.open();
// set key-value
await db.set('mykey', 'My value');
// get value
const result = await db.get('mykey');
console.log(result);
```
Output from the `console.log` statement:
```bash
My value
```
:::tip
The `db.set(key, value, options={})` API has an options part which lets you add a TTL value and a keyspace parameter. This is covered more in the part-5/6 of this tutorial.
:::
### Set a JSON value
Setting a JSON value in the key-value store is easy. Use the `JSON.stringify` function to convert your JavaScript object to a string value before inserting the value to the store. And use the `JSON.parse` function to convert it back to an Object when retrieving the value.
```js
const myObj = {
foo: 'bar',
price: 42,
};
const db = await Datastore.open();
// convert JSON to string
await db.set('myjsonkey', JSON.stringify(myObj));
// get value from key
const result = await db.get('myjsonkey');
// convert back to Object
console.log(JSON.parse(result));
```
Output from the `console.log` statement:
```bash
{ foo: 'bar', price: 42 }
```
### Set a binary value
Using the Node.js `Buffer` library we can set any string or binary value into the key-value store. Converting a buffer value to a `hex` string is a perfect solution for this use case.
```js
const db = await Datastore.open();
// create a hex value from a Buffer
const myBufVal = Buffer.from('Hello world!πππ', 'utf-8').toString('hex');
// set value
await db.set('mybufkey', myBufVal);
// get the value for key
const hexString = await db.get('mybufkey');
// convert hex string value back to a normal string
const stringVal = Buffer.from(hexString, 'hex').toString();
console.log(hexString, stringVal);
```
Output from the `console.log` statement:
```bash
48656c6c6f20776f726c6421f09f988ef09f988ef09f988e Hello world!πππ
```
### Set a number value
Numbers are converted to its string representation.
```js
const db = await Datastore.open();
// set value as Integer
await db.set('mynumkey', 42);
// get value for key
const result = await db.get('mynumkey');
console.log(result, 'is of type', typeof result);
```
Output from the `console.log` statement:
```bash
42 is of type string
```
### Setting multiple values
In some use cases you'll need to insert or set multiple values to the key-value store.
JavaScript solves this task for us with the `Promise.all` function. The key-value store `set` function returns a `Promise`, hence we can create an array of `set` functions that we pass to the `Promise.all` function. The code example below shows you how we can create multiple key-value inserts in one operation.
```js
const db = await Datastore.open();
const array = [
db.set('uno', 'one value'),
db.set('dos', 'two value'),
db.set('tres', 'three value'),
];
const result = await Promise.all(array);
console.log(result);
```
Output from the `console.log` statement:
```bash
[ 'one value', 'two value', 'three value' ]
```
### Getting multiple values
Similar to the technique we use for setting multiple values in the key-value store, we can also get multiple values with the `Promise.all` function.
```js
const db = await Datastore.open();
const array = [db.get('uno'), db.get('dos'), db.get('tres')];
const result = await Promise.all(array);
console.log(result);
```
Output from the `console.log` statement:
```bash
[ 'one value', 'two value', 'three value' ]
```
## Deleting values
Deleting values from the key-value store follows the same pattern as the `get` operation.
When deleleting a key-value pair, the value is returned if the key exists, otherwise a `null` value is returned. You can also use the `Promise.all` technique shown above for multiple delete operations. The code example below shows the basic delete operation.
```js
const db = await Datastore.open();
// delete key-value pair
const result1 = await db.del('uno');
// try to get the deleted key, should be null
const result2 = await db.get('uno');
console.log(result1, result2);
```
Output from the `console.log` statement:
```bash
uno null
```
This part-2 of the key-value store tutorial has shown how you can use the basic operations in serverless JavaScript functions with codehooks.io.
:::info Key-Value Store API documentation
You can find the documentation for the key-value store API [here](/docs/key-value-database-api).
:::
---
## Part-3: Increment and decrement operations
*(Note: This content contains MDX/JSX code)*
## Tutorial overview
- [Part 1 - Introduction to key-value store](part-1)
- [Part 2 - Basic operation get, set and delete key-values](part-2-basic-operations)
- **[Part 3 - Increment and decrement operations](part-3-increment-and-decrement-operations)**
- [Part 4 - Working with multiple values and streams](part-4-working-with-multiple-values-and-streams)
- [Part 5 - Managing data with TTL options](part-5-managing-data-with-ttl-options)
- [Part 6 - Multiple key spaces](part-6-multiple-key-spaces)
- [Part 7 - Interaction with the key-val store from the CLI](part-7-key-value-store-interaction-from-cli)
This is part-3 of the [key-value database tutorial](part-1) for [codehooks.io](https://codehooks.io) serverless JavaScript. In this part we'll focus on how to increment and decrement values in the built-in key-value database.
Increment and decrement operations are commonly supported in [key-value databases](https://en.wikipedia.org/wiki/Key%E2%80%93value_database), and they can be useful for a variety of applications.
One benefit of increment and decrement operations is that they allow you to update a value in the database without having to read the current value, update it, and then write the updated value back to the database. This can be more efficient than performing these steps separately, especially if the value is being updated frequently.
Increment and decrement operations can also be useful for maintaining counters or other types of numerical data in the database. For example, you could use an increment operation to count the number of times a user has visited a webpage, or you could use a decrement operation to reduce a stock quantity in an online store when an item is purchased.
Overall, increment and decrement operations can provide a simple and efficient way to update numerical values in a key-value database.
## Key-value store API for incrementing and decrementing values
The initial value of an incremented or decremented key-value is always 0. You can increment or decrement a value with any positive `integer`. The minimal code example below shows usage of the two functions `incr` and `decr` with the Datastore API.
```js
// connect to key-value store
const db = await Datastore.open();
// increment value
const val1 = await db.incr('myValue', 1);
// decrement value
const val2 = await db.decr('myValue', 1);
console.log('key-val', val1, val2);
// output: key-val, 1, 1
```
Lets continue with some more realistic and practical code examples.
## Code example: count user API traffic
The code example below shows a simple REST API endpoint that increments a counter in the key-value store each time it's called from a client app.
```js title="index.js"
import app from 'codehooks-js'; // codehooks lib for express style code
// serverless function
async function myFunc(req, res) {
// get userID from the URL, e.g. /myendpoint/9x00fbb
const { userID } = req.params;
// connect to key-value store
const db = await Datastore.open();
// increment api count by userID
const count = await db.incr(`API-count-${userID}`, 1);
res.json({ count }); // returns {"count": 1}, {"count": 2}, etc.
}
// Create a GET endpoint route with the serverless function
app.get('/myendpoint/:userID', myFunc);
export default app.init(); // Bind functions to the serverless cloud
```
Some example values in the key-value store, after some usage of the endpoint, might look like the key-values shown below:
```js
{ key: 'API-count-9x00fbb', value: 4 }
{ key: 'API-count-9x00fea', value: 1 }
{ key: 'API-count-out32xz', value: 5 }
{ key: 'API-count-qar870f', value: 3 }
```
## Code example: reduce stock quantity in an e-commerce online store
Lets say you fill and re-stock your warehouse by calling the key-value store API.
E.g. `db.set('in-stock-TeslaModelZ', 100)`, `db.set('in-stock-TeslaModelX', 45)`
Having an `in-stock` count of our products will enable us to create an endpoint for purchasing and reducing the in-stock count like shown in the code example below.
```js title="index.js"
import app from 'codehooks-js'; // codehooks lib for express style code
app.post('/checkout/:basketID', async (req, res) => {
// get basketID from the URL, e.g. /checkout/xyz123
const { basketID } = req.params;
// get quantity and product from POST body
const { quantity, product } = req.body;
// connect to key-value store
const db = await Datastore.open();
// decrement value
const stock = await db.decr(`in-stock-${product}`, quantity);
// handle case if stock === 0
res.json({ stock, product });
});
export default app.init(); // Bind functions to the serverless cloud
```
The code example below shows how a JavaScript client can `POST` a purchase and a quantity by calling this REST API endpoint:
```js title="client-app.js"
const MY_API_KEY = 'XXXXX'; // NB! Not for production. Use JWT and JWKS for secure API endpoints.
const MY_BASKET_ID = 'xyz123'; // example id
const headersList = {
Accept: '*/*',
'x-api-key': MY_API_KEY,
'Content-Type': 'application/json',
};
const bodyContent = JSON.stringify({
product: 'TeslaModelZ',
quantity: 1,
});
const response = await fetch(
'https://keyvaldemo-f0fe.api.codehooks.io/prod/checkout/' + MY_BASKET_ID,
{
method: 'POST',
body: bodyContent,
headers: headersList,
}
);
const data = await response.json();
console.log(data);
```
Example response:
```js
{
"stock": 97,
"product": "TeslaModelZ"
}
```
This part-3 of the key-value store tutorial has shown how you can use the increment and decrement operations in serverless JavaScript functions with [codehooks.io](https://codehooks.io).
:::info Key-Value Store API documentation
You can find the documentation for the key-value store API [here](/docs/key-value-database-api).
:::
---
## Part-4: Working with multiple values and streams
*(Note: This content contains MDX/JSX code)*
## Tutorial overview
- [Part 1 - Introduction to key-value store](part-1)
- [Part 2 - Basic operation get, set and delete key-values](part-2-basic-operations)
- [Part 3 - Increment and decrement operations](part-3-increment-and-decrement-operations)
- **[Part 4 - Working with multiple values and streams](part-4-working-with-multiple-values-and-streams)**
- [Part 5 - Managing data with TTL options](part-5-managing-data-with-ttl-options)
- [Part 6 - Multiple key spaces](part-6-multiple-key-spaces)
- [Part 7 - Interaction with the key-val store from the CLI](part-7-key-value-store-interaction-from-cli)
Welcome to this **part-4** of the [key-value database tutorial](part-1) for [codehooks.io](https://codehooks.io) serverless [Node.js](https://nodejs.org) backend. In this part we'll focus on how to work with multiple key-value pairs in a [key-value database](https://en.wikipedia.org/wiki/Key%E2%80%93value_database).
The Codehooks.io key-value database stores all keys as a sorted string index. This is a very useful feature if you need to store and retrieve multiple values together, as a group or as a segment of key-values.
## IoT device - time series data example
To illustrate this feature we'll create an example use-case for capturing data from multiple [IoT devices](https://en.wikipedia.org/wiki/Internet_of_things). In this example, each IoT device sends its location, identification and observations to a specific [REST API](https://en.wikipedia.org/wiki/Representational_state_transfer) network endpoint.
For example, a temperature sensor device in the "Alpha region West" reports its value each minute. And a wind force sensor in the "Beta region West" reports its values hourly, and so on.
To ensure that we can access and process the observation data sets effectiviely, we'll create a key string combination that captures all necessary information in one unique key-value pair.
To illustrate this concept lets imagine a series of IoT device observations as shown in the example below.
```bash title="Example IoT device observation data points"
'/Alpha/West/density/2023-01-09T10:14:00.495Z' => '4.4191'
'/Alpha/West/humidity/2023-01-09T17:01:00.821Z' => '0.7539'
'/Alpha/West/temp/2023-01-09T21:15:00.450Z' => '87.100'
'/Beta/West/wind/2023-01-19T21:57:00.316Z' => '5.9477'
...
```
Now that we understand the concept for our IoT database, lets begin the development of an example backend API for IoT sensors.
We'll create a serverless REST API endpoint to receive observations from many IoT devices. The API will store each observation with a logic key that effectively creates a [time series database](https://en.wikipedia.org/wiki/Time_series_database) with data points that we can explore and use in our application.
Lets get started with creating the first API to receive data observations from a device.
## REST API endpoint to POST a single observation
The REST API route takes in 3 parameters for a given device, region, location and device.
And the POST body must contain the actual device observation data point.
For example:
```bash
POST /observation/North/Plaza/humid03
{"observation": "4.02"}
```
The route parameters are combined into a unique key with a time stamp which gives a correct time series for the data points. The following table shows some example device observations.
| IoT devices time series | Data points |
| ---------------------------------------------- | ----------- |
| /East/BuilingA/temp01/2023-02-22T18:30:14.441Z | 21.4 |
| /North/Plaza01/humid3/2023-02-23T05:23:11.306Z | 12.5 |
| /East/BuilingB/temp02/2023-02-22T18:32:38.991Z | 19.0 |
The serverless function below implements a REST API endpoint route that can receive data from a device and insert it to the key-value database.
```js {7}
app.post('/observation/:region/:location/:device', async (req, res) => {
const { region, location, device } = req.params;
const { observation } = req.body;
const key = `/${region}/${location}/${device}/${new Date().toISOString()}`;
// string key e.g. '/North/Plaza/humid03/2023-02-23T05:23:11.306Z'
const conn = await Datastore.open();
const result = await conn.set(key, observation);
console.log(result);
res.status(201).end('Data received ok');
});
```
```bash title="Curl example post of a single observation"
curl -X POST \
'https://.api.codehooks.io/dev/observation/East/BuilingA/temp01' \
--header 'x-apikey: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"observation": "21.4"
}'
```
:::tip
Use the CLI command [`coho info --examples`](../../cli#info) to inspect your project API endpoint and Curl examples.
:::
## Inspecting the key-value database with the CLI
The [Codehooks CLI](../../cli.md#get) can be used to manage the key-value database basic operations, `get`, `set` and `del`. For example, to retrieve all observations for the `East` locations you can run the following command:
```bash
coho get '/East*'
```
This will list all matching keys in the key-value database as the example output shows below.
```bash title="command output"
{
key: '/East/BuilingA/temp01/2023-02-23T05:48:19.646Z',
value: '21.4'
}
{
key: '/East/BuilingA/temp01/2023-02-23T05:49:06.614Z',
value: '20.0'
}
2 keys
```
## Endpoint to GET observations subkey
Similiar to the CLI example above, we can use the [database API](../../nodekeyval.md) `getAll` method to retrieve all key-value pairs that match a start segment of a particular key. In this example we are producing [CSV data format](https://en.wikipedia.org/wiki/Comma-separated_values) for easy spreadsheet analysis etc.
```js {7}
app.get('/observations/:prefix', async (req, res) => {
const conn = await Datastore.open();
const { prefix } = req.params;
let count = 0;
res.set('content-type', 'text/csv');
res.write('Device, Data\n');
const stream = conn.getAll(`/${prefix}`);
stream
.on('data', (data) => {
const outString = `"${data.key}", ${data.val}\n`;
count++;
res.write(outString);
})
.on('end', () => {
res.end();
});
});
```
Testing the API with curl again.
```bash title="Curl example, list time series as CSV data"
curl -X GET \
'https://.api.codehooks.io/dev/observations/Alpha' \
--header 'x-apikey: ' \
```
```csv title="command output CSV data"
Device, Data
"/Alpha/Center/decibel/2023-01-09T11:11:31.218Z", 2.3428
"/Alpha/Center/decibel/2023-01-09T11:12:01.050Z", 6.0632
"/Alpha/Center/decibel/2023-01-09T13:13:30.541Z", 0.7196
"/Alpha/Center/decibel/2023-01-09T15:23:00.589Z", 9.7232
"/Alpha/Center/decibel/2023-01-09T15:34:00.520Z", 5.0089
"/Alpha/Center/decibel/2023-01-09T17:04:00.942Z", 9.1861
```
## Complete source code
The complete source code for our IoT backend application is shown below.
```js title="index.js"
import { app, Datastore } from 'codehooks-js'; // Standard JS lib for express style code
// list a serie of IoT device observations
app.get('/observations/:prefix', async (req, res) => {
const conn = await Datastore.open();
const { prefix } = req.params;
let count = 0;
res.set('content-type', 'text/csv');
res.write('Device, Data\n');
const stream = conn.getAll(`/${prefix}`);
stream
.on('data', (data) => {
const outString = `"${data.key}", ${data.val}\n`;
count++;
res.write(outString);
})
.on('end', () => {
res.write(`Result: ${count}`);
res.end();
});
});
// register a new IoT device observation
app.post('/observation/:region/:location/:device', async (req, res) => {
const { region, location, device } = req.params;
const { observation } = req.body;
// string key e.g. '/North/Plaza/humid03/2023-02-23T05:23:11.306Z'
const key = `/${region}/${location}/${device}/${new Date().toISOString()}`;
const conn = await Datastore.open();
const result = await conn.set(key, observation);
console.log(result);
res.status(201).end('Data received ok');
});
export default app.init(); // Bind functions to the serverless cloud
```
This part-4 of the key-value store tutorial has shown how you can combine smart keys with the database streaming capabilities to create kind of IoT time series database with [codehooks.io](https://codehooks.io).
:::info Key-Value Store API documentation
You can find the documentation for the key-value store API [here](/docs/key-value-database-api).
:::
---
## Part-5: Managing data with TTL options
*(Note: This content contains MDX/JSX code)*
## Tutorial overview
- [Part 1 - Introduction to key-value store](part-1)
- [Part 2 - Basic operation get, set and delete key-values](part-2-basic-operations)
- [Part 3 - Increment and decrement operations](part-3-increment-and-decrement-operations)
- [Part 4 - Working with multiple values and streams](part-4-working-with-multiple-values-and-streams)
- **[Part 5 - Managing data with TTL options](part-5-managing-data-with-ttl-options)**
- [Part 6 - Multiple key spaces](part-6-multiple-key-spaces)
- [Part 7 - Interaction with the key-val store from the CLI](part-7-key-value-store-interaction-from-cli)
Welcome to this **part-5** of the [key-value database tutorial](part-1) for [codehooks.io](https://codehooks.io) serverless [Node.js](https://nodejs.org) backend. In this part we'll cover the time-to-live (TTL) feature in the Codehooks key-value database.
The TTL database feature allows you to associate an expiration time with each key-value pair. Once the TTL for a particular key expires, the key-value pair is automatically removed from the database.
Some common use cases for the TTL features are listed below:
1. Cache Expiration: Implement TTL in your cache to automatically remove stale data and ensure fresh content is served to users, improving website performance.
2. Session Management: Set TTL for user sessions in your web application to automatically remove expired sessions, enhancing security and freeing up server resources.
3. Temporary Data Storage: Use TTL to store temporary data, such as shopping cart information or form submissions, and automatically remove it after a certain period to avoid cluttering the database.
4. Logging: Apply TTL to store logs or activity data for a limited duration, ensuring the database remains focused on recent and relevant information.
5. Rate Limiting: Utilize TTL to enforce rate limits for API calls or user interactions, automatically resetting counters or restrictions after a specific time interval.
## Example code using the TTL feature
The example code below shows how to create a REST API to POST a temporary state value to a particular key. The state will be kept for 24 hours by the TTL feature.
```js
// keep state for 24 hours
app.post('/state/:id', async (req, res) => {
const ONE_DAY = 86400000; // One day in millis
// get id from request parameter
const { id } = req.params;
// state is the raw JSON body
const state = req.body;
// connect to data store
const conn = await Datastore.open();
const opt = {
ttl: ONE_DAY,
};
// set state with TTL option
const result = await conn.set(`state-${id}`, JSON.stringify(state), opt);
res.json(result);
});
```
Furthermore, the next code example shows how we can create a REST API to retrieve the state value within the 24 hour TTL time frame. If the state exists is is returned, otherwise a 404 status code is returned.
```js
// get state or 404 if TTL expired
app.get('/state/:id', async (req, res) => {
// get id from request parameter
const { id } = req.params;
const conn = await Datastore.open();
// get state from database or NULL if key is deleted
const result = await conn.get(`state-${id}`);
if (result === null) {
res.status(404).send('No state');
} else {
res.set('content-type', 'application/json');
res.send(result);
}
});
```
These simple examples shows how easy it is to implement various use cases with the TTL feature.
Overall, the TTL feature in the key-value database provides flexibility and automation for managing data expiration, allowing you to optimize performance, storage, and overall system efficiency.
:::info Key-Value Store API documentation
You can find the documentation for the key-value store API [here](/docs/key-value-database-api).
:::
---
## Part-6: - Multiple key spaces
*(Note: This content contains MDX/JSX code)*
## Tutorial overview
- [Part 1 - Introduction to key-value store](part-1)
- [Part 2 - Basic operation get, set and delete key-values](part-2-basic-operations)
- [Part 3 - Increment and decrement operations](part-3-increment-and-decrement-operations)
- [Part 4 - Working with multiple values and streams](part-4-working-with-multiple-values-and-streams)
- [Part 5 - Managing data with TTL options](part-5-managing-data-with-ttl-options)
- **[Part 6 - Multiple key spaces](part-6-multiple-key-spaces)**
- [Part 7 - Interaction with the key-val store from the CLI](part-7-key-value-store-interaction-from-cli)
Welcome to this **part-6** of the [key-value database tutorial](part-1) for [codehooks.io](https://codehooks.io) serverless [Node.js](https://nodejs.org) backend.
## Introducing Key-Value with Isolated Key Spaces
Key-Value databases are powerful tools for storing and retrieving data using a simple key-based approach. They offer flexibility, performance, and scalability for various applications. With the concept of isolated key spaces, you can take the benefits of Key-Value databases a step further.
Isolated key spaces allow you to logically separate different sets of keys within the same key-value database. Each isolated key space acts as an independent namespace, providing data isolation and organization. This means you can have multiple sets of keys with the same names, and they won't interfere with each other.
The [key-value database API](/docs/key-value-database-api) supports isolated key spaces for all API methods: `set`, `get`, `getAll`, `incr`, `decr`, `del` and `delAll`.
To set a key-value in an isolated key space you use the `keyspace` option value in each API method.
```js
const conn = await Datastore.open();
const option = {
keyspace: 'foospace',
};
const result = await conn.set('somekey', 'some value', option);
console.log(result);
```
Similarily to get a key-value from the isolated key space the same option applies.
```js
const conn = await Datastore.open();
const option = {
keyspace: 'foospace',
};
const result = await conn.get('somekey', option);
console.log(result);
```
This feature is particularly useful in scenarios where you need to store data from different sources or applications within a single database. By leveraging isolated key spaces, you can maintain data separation, avoid key collisions, and manage your data more efficiently.
Isolated key spaces enable you to:
- Organize Data: Group related keys together within isolated key spaces, making it easier to manage and maintain your data.
- Prevent Key Collisions: With isolated key spaces, keys with the same name can coexist without conflicts, as they belong to separate namespaces.
- Achieve Data Isolation: Keep data from different applications or components separate, ensuring integrity and avoiding unintended data interactions.
- Simplify Access Control: Apply different access permissions and security measures to each isolated key space, providing granular control over data access.
- Enhance Scalability: Isolated key spaces allow for horizontal scaling by distributing data across multiple instances or clusters.
By utilizing key-value databases with isolated key spaces, you can improve data organization, scalability, and security while maintaining the simplicity and efficiency of key-value storage.
:::info Key-Value Store API documentation
You can find the documentation for the key-value store API [here](/docs/key-value-database-api).
:::
---
## Part-7: - Interaction with the key-val store from the CLI
*(Note: This content contains MDX/JSX code)*
## Tutorial overview
- [Part 1 - Introduction to key-value store](part-1)
- [Part 2 - Basic operation get, set and delete key-values](part-2-basic-operations)
- [Part 3 - Increment and decrement operations](part-3-increment-and-decrement-operations)
- [Part 4 - Working with multiple values and streams](part-4-working-with-multiple-values-and-streams)
- [Part 5 - Managing data with TTL options](part-5-managing-data-with-ttl-options)
- [Part 6 - Multiple key spaces](part-6-multiple-key-spaces)
- **[Part 7 - Interaction with the key-val store from the CLI](part-7-key-value-store-interaction-from-cli)**
Welcome to this **part-7** of the [key-value database tutorial](part-1) for [codehooks.io](https://codehooks.io) serverless [Node.js](https://nodejs.org) backend.
In this section we'll cover how you can work with the key-value database using the Command Line Interface (CLI).
The Codehooks.io [CLI](/docs/cli) is an essential component of many developers' workflows, this is also true when working with the key-value database.
The CLI har 3 basic operations for working with the key-value database:
1. Set a value: `coho set `
2. Get a value: `coho get `
3. Delete a value: `coho del `
Let's go through some simple CLI examples showing how to work with the database keys and values.
## Setting a new key-value
```bash
coho set 'my-first-value' 'This is the first value'
# CLI output
{ key: 'my-first-value', value: 'This is the first value' }
```
## Setting another key-value
```bash
coho set 'my-second-value' 'This is the second value'
# CLI output
{ key: 'my-second-value', value: 'This is the second value' }
```
## Getting a key-value
```bash
coho get 'my-second-value'
# CLI output
{ key: 'my-second-value', value: 'This is the second value' }
```
## Getting multiple key-value(s) by key pattern
```bash
coho get 'my-*'
# CLI output
{ key: 'my-first-value', val: 'This is the first value' }
{ key: 'my-second-value', val: 'This is the second value' }
2 keys
```
## Setting a key-value with Time To Live (TTL)
```bash
coho set 'my-short-lived-value' 'I will be gone in 60 seconds' --ttl 60000
# CLI output
{ key: 'my-short-lived-value', value: 'I will be gone in 60 seconds' }
```
## Deleting a key-value
```bash
coho del 'my-first-value'
# CLI output
{ key: 'my-first-value' }
```
This concludes our seven parts tutorial on how to develop with the key-value store in codehooks.io.
:::info Key-Value Store API documentation
You can find the documentation for the key-value store API [here](/docs/key-value-database-api).
:::
---
## Examples - templates and integrations
*(Note: This content contains MDX/JSX code)*
import { styled } from '@mui/material/styles';
import alpineLogo from './images/alpine-logo.png';
import auth0Logo from './images/auth0-logo.png';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
import CardMedia from '@mui/material/CardMedia';
import chatgptLogo from './images/chatgpt-logo.png';
import graphQlLogo from './images/graphql-logo.png';
import Grid from '@mui/material/Grid';
import Link from '@docusaurus/Link';
import mailgunLogo from './images/mailgun-logo.png';
import mongodbLogo from './images/mongodb-logo.png';
import Paper from '@mui/material/Paper';
import reactLogo from './images/react-logo.png';
import nextLogo from './images/next-logo.png';
import svelteLogo from './images/svelte-logo.png';
import s3Logo from './images/s3-logo.png';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import GitHubIcon from '@mui/icons-material/GitHub';
import Admonition from '@theme/Admonition';
import {
CodeBracketIcon,
DocumentTextIcon,
ShoppingCartIcon,
EnvelopeIcon,
} from '@heroicons/react/24/outline';
export const UseCase = ({
icon,
link,
title = 'No title',
content =
);
export const SmallFeature = ({
image,
link = '#',
title = 'No title',
content =
body text here
,
index = -1,
nextsteps = [],
}) => {
return (
{title}
{index > -1 ? (
{' '}
) : null}
{content}
{nextsteps.length > 0 && (
Examples
{nextsteps.map((step, i) => (
{step}
))}
)}
);
};
Learning through examples is one of the most effective ways to master new technologies.
Here we provide practical hands-on examples you can learn from and build on.
Our examples are divided into two main categories:
- [**Technology Integrations**](#technology-integrations): Step-by-step guides showing how to integrate Codehooks with popular frameworks and services like [React](https://react.dev/), [Next.js](https://nextjs.org/), [Auth0](https://auth0.com/), and more. These examples demonstrate basic setups and best practices for specific technologies.
- [**Template Applications**](#template-applications): Complete, production-ready applications you can deploy immediately or use as a foundation for your own projects. These templates include all necessary frontend and backend code, along with detailed documentation.
## Technology integrations
Learn how to use codehooks.io as an API backend for your React app
,
Create a random quotes generator with React and a NoSQL database
,
React ToDo app with user Auth0.com authentication
,
]}
/>
Next.js todo app with complete code example
,
More coming up soon ...,
]}
/>
Svelte todo app with complete code example
,
More coming up soon ...,
]}
/>
See how Codehooks.io can act as a complete GraphQL backend. We'll
demonstrate handling GraphQL queries and mutations and storing data in a
codehooks database.
}
nextsteps={[
How to create a GraphQL-powered persistent backend with codehooks.io's
built-in NoSQL database
,
]}
/>
Discover how Codehooks.io integrates with mongoDB, giving you full
control of data ownership and management. Our examples will show you the
straightforward process of connecting Codehooks.io with your MongoDB
setup.
}
nextsteps={[
Connecting to an external mongoDB database
,
Create a CRUD REST API for your NoSQL database
,
]}
/>
Here we show you ways you can get ChatGPT to code the backend for you.
You can also explore how to add AI-powered chat functionalities with
ChatGPT. We'll cover integration techniques for incorporating
conversational AI into your applications.
}
nextsteps={[
Let ChatGPT code your backend API
,
How to use ChatGPT to build Backend APIs with codehooks.io
,
]}
/>
Understand the integration of Auth0 with Codehooks.io for
authentication. We'll delve into secure authentication flow
implementations, emphasizing JWT and security best practices.
}
nextsteps={[
Auth0.com boilerplate React example app
,
ToDo app with Auth0.com authentication
,
]}
/>
Learn about leveraging Codehooks.io with AWS S3 for data storage
solutions. We'll explore simple file retrieval and storage.
}
nextsteps={[
Build an API for file upload and download with AWS S3 (v3)
,
]}
/>
Dive into the integration of Mailgun for email functionality in your
applications with Codehooks.io.
}
nextsteps={[
Integrating with Mailgun REST API
,
Send bulk emails using Queues and Mailgun
,
]}
/>
Examine how Alpine.js' lightweight approach complements Codehooks.io's
backend capabilities. We'll look at building interactive web components
with easy backend integration.
}
nextsteps={[
Tutorial: create an Alpine.js app with a NoSQL backend
,
DaisyUI + Alpine.js + Codehooks.io - the simple web app trio
,
]}
/>
## Template Applications
### Codehooks Analytics
Codehooks Analytics is an open-source web analytics tool for tracking website and app traffic, giving you full control over your data and dashboard. You can deploy it as-is, customize it, or build your own solution.
Unlike third-party services, this tool avoids costly per-event fees, offers real-time data access, and eliminates vendor lock-in. It does not use cookies like most other analytics solutions.
Built with [Codehooks.io](https://codehooks.io) for the backend and [Alpine.js](https://alpinejs.dev/), [TailwindCSS](https://tailwindcss.com/), and [DaisyUI](https://daisyui.com/) for the frontend, it's easy to use and modify. The Codehooks.io free Developer plan covers most use cases unless your site is very busy.
- **Codehooks.io**: Simplifies API and database management.
- **Alpine.js**: Adds interactivity with minimal complexity.
- **TailwindCSS**: Enables rapid UI development.
- **DaisyUI**: Provides pre-built components for faster design.
- **OpenAI** API: Provides daily AI insights from your data.
}
title="Get started with the Codehooks analytics template on Github"
>
All you need to host the whole solution and start tracking your custom
analytics data is a codehooks.io project/space and the free Development plan.
The source code and complete installation and setup instructions can be found
in [the Github repository](https://github.com/RestDB/webanalytics). Feel free
to fork it, submit pull requests or give it a star.
### Codehooks Authentication
Codehooks Authentication is an open-source authentication library that provides a complete authentication solution for your web applications. Unlike commercial providers, this library gives you full control over your authentication process and user data while maintaining simplicity and ease of use.
Built with security and flexibility in mind, it supports multiple authentication methods including:
- One-time password (OTP) authentication
- OAuth integration (Google and GitHub)
- JWT-based access and refresh tokens
- Email-based user verification
The authentication system integrates seamlessly with:
- **Codehooks.io**: For backend API and database management
- **Multiple email providers**: Including Mailgun, Postmark, and SendGrid
- **Custom templates**: Fully customizable HTML/CSS templates for all auth pages
- **OAuth providers**: Ready-to-use integrations with major providers
}
title="Get started with the Codehooks authentication template on Github"
>
All you need is a codehooks.io project/space to implement a complete
authentication system. The source code and complete installation and setup
instructions can be found in [the Github
repository](https://github.com/RestDB/codehooks-auth). The library is
available as an [npm package](https://www.npmjs.com/package/codehooks-auth)
and includes all necessary templates and assets.
### Directory Template
This template contains everything you need to build your own directory service. Directory services are crucial for organizing and presenting information in a structured way. They're used for everything from product catalogs to service listings, and their success often depends on two key factors:
- SEO optimization for discoverability
- Developer-friendly implementation
Built-in features:
- Server-side rendering using HTML/Handlebars
- Built-in SEO optimization
- Modern UI using DaisyUI and Tailwind CSS
- Automatic screenshot generation
- Sitemap generation
- Full client-side text search with lunrjs
- Mock data support for rapid development
- Easy deployment to Codehooks.io
#### Examples in the wild
- [AllToolz.dev](https://alltoolz.dev) - a directory of developer tools
- [DigiHub.no](https://bergen.digihub.no) - a directory of web agencies and consulting companies in Bergen, Norway
}
title="Get started with the Directory template on Github"
>
All you need is a codehooks.io project/space to start creating your own
directory service. The source code and complete installation and setup
instructions can be found in [the Github
repository](https://github.com/RestDB/directory-template).
---
## Overview
*(Note: This content contains MDX/JSX code)*
import TOCInline from '@theme/TOCInline';
import myImageUrl from './images/quickstart/collection-with-data.png';
import CodeBlock from '@theme/CodeBlock'
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
import Link from '@docusaurus/Link';
import Badge from '@mui/material/Badge';
import {Button, Grid} from '@mui/material';
import OverviewPng from './images/Overview-1.png';
import Studio1Png from './images/studio/studio1.png';
import Studio2Png from './images/studio/studio2.png';
import Studio3Png from './images/studio/studio3.png';
import Studio4Png from './images/studio/json-schema.png';
import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet';
import FolderOpenIcon from '@mui/icons-material/FolderOpen';
import LayersIcon from '@mui/icons-material/Layers';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import {
CogIcon,
CheckCircleIcon,
CheckIcon,
DocumentCheckIcon,
RocketLaunchIcon,
CloudArrowUpIcon,
MagnifyingGlassPlusIcon,
LockClosedIcon,
PlayIcon,
CircleStackIcon,
DocumentPlusIcon,
KeyIcon,
ClockIcon,
RectangleStackIcon,
ChartBarSquareIcon,
LinkIcon,
QueueListIcon,
SparklesIcon
} from '@heroicons/react/24/outline';
export const NumberIcon = ({ index = -1, style = {}}) => (
{index}
);
export const codex0 = `import {app} from 'codehooks-js'
// Use Crudlify to create a REST API for any database collection
app.crudlify()
// bind to serverless runtime
export default app.init();
`;
export const codex1 = `const URL = 'https://myproject-fafb.api.codehooks.io/dev/orders';
response = await fetch(URL+'?status=OPEN', /* options */)
const result = await response.json()
`;
export const codex2 = `import {app, datastore} from 'codehooks-js'
// REST API route
app.get('/sales', async (req, res) => {
// highlight-next-line
const conn = await datastore.open();
const query = { status: "OPEN" };
const options = { sort: { date: 1 } }
conn.getMany('orders', query, options).json(res); // stream JSON to client
})
// bind to serverless runtime
export default app.init();
`;
export const codex3 = `import {app, datastore} from 'codehooks-js'
// REST API route
app.post('/cacheit', async (req, res) => {
const conn = await datastore.open();
const opt = {ttl: 60*1000};
// highlight-next-line
await conn.set('my_cache_key', '1 min value', opt);
res.send('Look dad, I have my own cache now!');
})
// bind to serverless runtime
export default app.init();
`;
export const codex4 = `import {app} from 'codehooks-js'
// serve the assets directory of the deployed project source code
// highlight-next-line
app.static({directory: "/assets"})
// serve a direcory of Blob storage files
// highlight-next-line
app.storage({route:"/documents", directory: "/myblobs"})
// CRUD REST API
crudlify(app)
// bind to serverless runtime
export default app.init();`;
export const codex5 = `import app from 'codehooks-js'
// Trigger a function with a CRON expression
// highlight-next-line
app.job('*/5 * * * *', (_, job) => {
console.log('Hello every 5 minutes')
// Do stuff ...
job.end()
});
export default app.init();`;
export const codex6 = `import { app, Datastore } from 'codehooks-js'\n
// register a Queue task worker
// highlight-next-line
app.worker('ticketToPDF', (req, res) => {
const { email, ticketid } = req.body.payload;
//TODO: implement code to fetch ticket data, produce PDF
// and send email with attachment using Mailgun, Sendgrid or Amazon SES
// ...and implement error handling
res.end(); // done processing queue item
})\n
// REST API
app.post('/createticket', async (req, res) => {
const { email, ticketid } = req.body;
const conn = await Datastore.open();
// highlight-next-line
await conn.enqueue("ticketToPDF", { email, ticketid });
res.status(201).json({"message": \`Check email $\{email\}\`});
})\n
export default app.init();
`;
export const codex7 = `import { app, Datastore, aggregation } from 'codehooks-js'
... // collapsed code
const spec = {
$group: {
$field: "month",
$max: ["sales", "winback"],
$sum: "sales"
}
}
const db = await datastore.open();
const dbstream = db.getMany('salesData');
// highlight-next-line
const result = await aggregation(dbstream, spec)
... // collapsed code
`;
export const codex8 = `{
"oct": {
"sales": {
"max": 1477.39,
"sum": 1234.05
},
"winback": {
"max": 22.0
}
},
"nov": {
"sales": {
"max": 2357.00,
"sum": 5432.00
},
"winback": {
"max": 91.0
}
},
}`;
export const SmallFeature = ({
icon,
link = '#',
title = 'No title',
content =
);
};
## Codehooks: Integrate and Automate everything
Codehooks provides powerful tools for developers to build, connect, and process APIs with ease. Create projects using our intuitive command line interface or web-based admin UI. A Codehooks.io project contains multiple isolated spaces - complete self-contained environments with their own database, settings, and code.
The **[`codehooks-js`](https://www.npmjs.com/package/codehooks-js)** library is the foundation for all Codehooks JavaScript development, offering a comprehensive set of 7 specialized APIs that accelerate development, streamline data integration, and simplify data processing. Explore each capability below to see how Codehooks can improve your development experience.
:::tip API Cheat Sheet
Check out the [API Cheat Sheet](/docs/apicheatsheet) for a quick overview of all the APIs
:::
If you're like most developers (i.e. smart and lazy π), we've added a [ChatGPT and LLM Prompt](/docs/chatgpt-backend-api-prompt) you can use to kickstart new projects. The prompt itself is worth looking at to get an overview of the features of the codehooks-js library. Our new MCP Server implementation has this prompt built-in.
:::tip Codehooks CLI tool
To deploy code (and much more), you use the Codehooks CLI tool:
`npm install codehooks -g`
Code editing and deployment will also soon be available in the web based UI.
:::
## API development
}
title="Get a flying start with an automatic CRUD REST API"
content={
Consider a Codehooks project, for example myproject-fafb.
Under the hood, Codehooks deploys an automatic REST API which is made up
of the following really short - but complete application code:
{codex0}
It's just 3 lines of code π₯, and it delivers a fully secure CRUD REST
API with a NoSQL database for JSON queries and updates. Client apps can
instantly connect to your data API, for example from a JavaScript app
like the example code snippet shown below:
{codex1}
}
nextsteps={[
Database REST API detailed docs,
Data validation and schema support
,
Develop custom REST APIs,
Client code examples,
Cheat Sheet for all the APIs,
]}
/>
## NoSQL and Key-Value Datastores
}
title="Easy database API inspired by MongoDB"
content={
Sometimes a standard CRUD REST API isn't enough. With custom JavaScript
functions you can create logic that integrates directly to the NoSQL
datastore. No need to think about drivers, versions, protocol or
security, the database is just there, available as an intrinsic object
in every serverless function.
{codex2}
}
nextsteps={[
Getting started,
Database API,
NoSQL query language
,
Database indexing,
Check output logs with the CLI,
Query data with the CLI,
]}
/>
}
title="Speed up your application with a Key-Value cache"
content={
The Codehooks data engine includes a simple key-value datastore. A
key-value datastore has many use-cases, and is ideal for caching due to
the swift data access and simple, maintenance-friendly architecture. A
simple example is shown below, where a string is stored for 60 seconds
with the ttl option.
{codex3}
}
nextsteps={[
Key-Value API,
Tutorials,
]}
/>
## Background Jobs
}
title="CRON expressions + your function keeps running"
content={
Use simple CRON expressions to automate and manage the execution of
routine tasks. You can schedule a job to run at multiple, disparate
times. For example, you can schedule a task to run at 2 AM on Sundays
and 4 PM on Wednesdays with the same CRON job.
In the simple example below, the CRON expression{' '}
*/5 * * * * will trigger a JavaScript function each 5
minutes.
}
title="Scale workloads with queues and worker functions"
content={
Use persistent queues and "worker" functions to create robust,
efficient, and highly responsive back-end systems. Queues scales
processing across multiple nodes and will restore and continue working
in case of errors and system failures.
Check out the example below, where a REST API request starts a complex
task by adding it to the Queue for later processing.
Create and deploy robust, scalable workflows using queued functions and state management.
Build reliable backend systems with automatic retry, state persistence, and distributed processing.
The example below shows a simple workflow that processes customer onboarding with email validation.
{`import { app } from 'codehooks-js';
const workflow = app.createWorkflow('customerOnboarding', 'Customer signup and email validation', {
START: async function (state, goto) {
state = { email: state.email, validated: false };
await sendWelcomeEmail(state.email);
goto('waitForValidation', state);
},
waitForValidation: async function (state, goto) {
if (state.validated) {
goto('complete', state);
} else {
wait({wait_message: 'Waiting for subflow to complete'});
}
},
complete: function (state, goto) {
state.status = 'completed';
goto(null, state); // workflow complete
}
});
// Start workflow from REST API
app.post('/start-onboarding', async (req, res) => {
const result = await workflow.start({ email: req.body.email });
res.send(result);
});`}
}
nextsteps={[
Workflow API,
Example Workflows
]}
/>
## File System & Blob storage
}
title="Serve any document or file content"
content={
Codehooks has a built-in file system to support streaming file uploads
and downloads. You can serve files via a static fileserver or via
JavaScript functions using the File API. The example CLI command below
shows how to upload a directory of files to the Blob storage{' '}
dev space (environment) of the project. You can publish
documents and files in two ways. Use the file-upload CLI command for
mass uploads of files/directories:
Or deploy file content located as part of your server code with the
deploy command:
$ coho deploy
The example below shows how to serve both deployed files and Blob
storage content.
{codex4}
}
nextsteps={[
File API,
Upload files with the CLI,
]}
/>
## Data Aggregation
}
title="Easy to use data aggregation format"
content={
Transform your live data streams into insights with just a few lines of
code. Codehooks has a simple but powerful JSON-based aggregation
expression language to specify data transformations. Groups and sums can
be nested to create both simple and advanced data outputs. The
aggregation API can process input from any JSON data-stream, for example
the query output from the NoSQL datastore. π₯
{codex7}
The example aggreagation transformation produces JSON output similar to
the example below.
{codex8}
}
nextsteps={[Aggregation API]}
/>
## Next Steps
Read this far? Great π
It's time to get your feet wet π¦. Why don't you start thinking about how codehooks' toolbox can help you build your solution. You can get started with the [quickstart for developers](/docs/quickstart-cli) or just [sign up](https://account.codehooks.io) and create your project and space using the web UI. You can easily upload some CSV data and immediately test your new API.
The documentation or tutorials are great places to start. If you need more help, feel free to contact us on the chat.
---
## Send bulk emails using Mailgun and serverless Javascript hooks
*(Note: This content contains MDX/JSX code)*
Email remains the top communication tool for businesses.
Either, if you need to create simple direct emails messages, or schedule templated bulk messages to customers - we all need flexible tools to communicate effectively with our customers.
This blog post shows how you can create a serverless background application for sending bulk HTML emails using the popular [Mailgun SaaS](https://mailgun.com).

This blog post also demonstrates some useful Codehooks concepts, such as [background cron jobs](/docs/jobhooks), [message queues](/docs/queuehooks), [NoSql database](/docs/nosql-database-api), [key-value store](/docs/key-value-database-api) and [secret environment variables](/docs/cli#set-env).
Read on to learn more about effective email processing with Mailgun and Codehooks.
:::info Github repo
[Check out the full source code available on GitHub.](https://github.com/RestDB/codehooks-io-examples/tree/main/mailgun)
:::
## Quick preview: example application usage
Our example application runs in the background and monitors a mailing list (data collection) and sends an email to each contact in the list using a HTML template.
New entries to the mailing list are POST'ed via a REST API endpoint or imported via the CLI tool. For scalability and resilience all emails are processed with the [Codehooks message queue](/docs/queuehooks) and serverless function workers.
After deploying the application, we can use the CLI to import a CSV file with mail addresses to the application collection `mailinglist`.
```bash
coho import --filepath ./mailinglist.csv --collection mailinglist
```
The running background process (cron job) picks up the new entries and sends each contact item to the message queue for processing by a worker function, which integrates with Mailgun to send the actual message.
We can monitor the process in realtime using the `coho logs -f` command shown below.
```bash
coho logs -f
dev 2022-09-27T20:30:00.717Z Email process starting 2 contacts
dev 2022-09-27T20:30:10.039Z joe@example.com 1
dev 2022-09-27T20:30:11.133Z Mailgun reciept <20220927203011.860b8f5aa23e8e08@mg.codehooks.io>
dev 2022-09-27T20:30:11.324Z jane@example.com 1
dev 2022-09-27T20:30:12.449Z Mailgun reciept <20220927203012.6effcf0efe50fa70@mg.codehooks.io>
```
Now that we understand the basic use case of this application, let's go ahead and see how it's made, and how you can create and modify it yourself.
## Get a Mailgun account
If you haven't already, navigate to the [Mailgun](https://www.mailgun.com/) website and create a new account, it's free to sign up. Follow the instructions, and notice the domain, API-url and API-key information needed in this article. To integrate the Codehooks serverless function we'll use the [Mailgun REST API](https://documentation.mailgun.com/en/latest/quickstart-sending.html#how-to-start-sending-email).
With a Mailgun account ready, we can go forward with creating a new project for our Codehooks application.
## Create a new Codehooks project
First we need to create a new Codehooks project for the source code and a deployment endpoint. You can clone the GitHub project, like in the example code below, or you can create the source files yourself.
```bash
git clone https://github.com/RestDB/codehooks-io-examples.git
cd codehooks-io-examples/mailgun
coho init
```
Then we install the neccessary libraries `codehooks-js`, `node-fetch` and `form-data`.
```bash
npm i codehooks-js form-data node-fetch --save
```
After the initialization your project directory should contain these files.
```bash
mailgun
βββ README.md
βββ config.json
βββ index.js
βββ mailinglist.csv
βββ node_modules
βββ package-lock.json
βββ package.json
βββ template.js
```
## Email HTML template
In this example we'll be using a standard HTML email template, actually this is similar to the ones we use in Codehooks for email communication.
The `template.js` file contains the HTML code and logic to merge email addresses and contact data into an email message that looks good on both mobile and desktop. Check out the [source code](https://github.com/RestDB/codehooks-io-examples/blob/main/mailgun/template.js) to change it to your needs.
The example screenshot below shows the email template in action.

## Send email with the Mailgun REST API
`index.js` contains all serverless functions and logic for the application. The `senOneEmail` function implements our Mailgun integration with two parameters: emailaddress and name.
```js
// Send mail with Mailgun REST API
async function sendOneEmail(emailaddress, name) {
// create an email as form data
const form = new FormData();
form.append('from', 'james@example.com');
form.append('to', emailaddress);
form.append('subject', 'Testing Mailgun with Codehooks');
form.append('html', createWelcomeHTML({ emailaddress, name }));
// Mailgun api endpoint
const url = `https://${MAILGUN_URL}/v3/${process.env.MAILGUN_DOMAIN}/messages`;
// Mailgun credentials must be base64 encoded for Basic authentication
const credentials = Buffer.from(`api:${process.env.MAILGUN_APIKEY}`).toString(
'base64'
);
// POST REST API with the email form data
const resp = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Basic ${credentials}`,
},
body: form,
});
// handle response errors or OK data
if (resp.status <= 201) {
return res.json();
} else {
throw new Error(`${resp.status} ${resp.statusText}`);
}
}
```
## Create environment variables as secrets
It is important to protect your private secrets used to access the Mailgun API.
The Codehooks CLI command `coho set-env VARIABLENAME xxyyzz --encrypted` can create encrypted enviroment variables that you can use in your code as `process.env.VARIABLENAME`, without exposing their content.
The CLI command shown below shows how we have added the secret keys for our application.
```bash
coho set-env MAILGUN_APIKEY XXXX --encrypted
coho set-env MAILGUN_DOMAIN XXXX --encrypted
```
:::tip
You can also set these environment variables using the account UI.
:::
## REST API to POST email data
As shown in the initial example, you can use the CLI to import data to the mailinglist collection. But we also need an API to do the same.
In Codehooks this is done with the `app.post` hook, which creates a serverless function that is bound to a network endpoint route.
:::info
Use the `coho info` command to see your project's network endpoint and API tokens.
:::
In our example it's: `https://mailgun-xyz1.api.codehooks.io/dev/email`
The API receives a `POST` body with email data that is inserted into the `mailinglist` collection, which again is processes by the background job and queue.
```js
// REST API serverless function
app.post('/email', async (req, res) => {
const conn = await Datastore.open();
// add contact to mailinglist data collection
const doc = await conn.insertOne('mailinglist', req.body);
// return new document to client
res.status(201).json(doc);
});
```
Example curl command to test the REST API is shown below.
```bash
curl -X POST \
'https://mailgun-xyz1.api.codehooks.io/dev/email' \
--header 'x-apikey: c748466e-xxxx-yyyy-zzzz-d8a10ae16b24' \
--header 'Content-Type: application/json' \
--data-raw '{
"emailaddress": "jones@codehooks.io",
"name": "Eric Jones"
}'
```
## Background CRON job
Codehooks has built in support for background job processing, called [Jobhooks](/docs/jobhooks).
In our application we schedule a new job that triggers our serverless function each 30th second ( with the cron expression `*/30 * * * * *`).
The logic in this function queries the database for any unsent emails, and adds them to the queue in the same query (using the `enqueueFromQuery` [awesome feature π](/docs/queueapi#enqueuefromquerycollection-query-topic-options)). If you like, you can add more jobs with other schedules and email data for flexibility, Codehooks has many options.
```js
// cron job serverless function to process unsent emails
app.job('*/30 * * * * *', async (req, res) => {
try {
const conn = await Datastore.open();
// Check key-val store for the running flag
const isRunning = await conn.get('running', { keyspace: 'emailprocess' });
console.log(isRunning);
// No process is running, start sending new emails
if (isRunning === null) {
// Add all emails that is not sent to queue
const job = await conn.enqueueFromQuery(
'mailinglist',
{
$or: [
{ attempt: { $gt: 1, $lt: 3 } },
{ attempt: { $exists: false } },
],
},
'emailQueue'
);
if (job.count > 0)
console.log('Email process starting', job.count, 'contacts');
}
res.end();
} catch (ex) {
console.error(ex);
res.end();
}
});
```
## Worker Queue
The built in Worker queue subscribes to messages published on a specific topic, in this application the topic is `emailQueue`.
Messages contain email contacts from the data collection.
Each message is processed and sent via the Mailgun function.
If it fails, we increase the attempts property using the [`$inc` operator](/docs/dataoperators#inc-operator).
This is used to retry up to 3 times before giving up.
```js
// Queue topic serverless worker function
app.worker('emailQueue', async (req, res) => {
const conn = await Datastore.open();
try {
// signal that we are processing emails
await conn.set('running', new Date().toISOString(), {
ttl: 1000 * 10,
keyspace: 'emailprocess',
});
const { emailaddress, name } = req.body.payload;
// inc attempt counter
const entry = await conn.updateOne('mailinglist', req.body._id, {
$inc: { attempt: 1 },
});
// log email and attempt
console.log(entry.emailaddress, entry.attempt);
// call Mailgun function
const reciept = await sendOneEmail(emailaddress, name);
// print Mailgun response
console.log('Mailgun reciept', reciept.id);
// update database object with sent timestamp
await conn.updateOne('mailinglist', req.body._id, {
emailSent: new Date().toISOString(),
});
// finish queue worker
res.end();
} catch (ex) {
// inc attempt counter
console.error('Send mail failed', ex);
const entry = await conn.updateOne('mailinglist', req.body._id, {
$inc: { attempt: 1 },
});
res.end();
}
});
```
## Inspect the `mailinglist` data collection
At any time you can inspect your data collections using the CLI command `coho query`. From our example we see that all emails is sent ok on the first attempt.
```bash
coho query mailinglist --pretty
{
emailaddress: 'joe@example.com',
name: 'Joe Ross',
_id: '18384e9de35-03ra2l4a3ul3od',
attempt: 1,
emailSent: '2022-09-28T16:24:39.598Z'
}
{
emailaddress: 'jane@example.com',
name: 'Jane Ross',
_id: '18384e9de35-4cmj21pyo4tjd4',
attempt: 1,
emailSent: '2022-09-28T16:24:39.719Z'
}
```
## Summary
Although Mailgun has their own excellent Bulk email service, this blogpost has provided you with some useful examples of how you can create a powerful backend service with only a little JavaScript code and the codehooks.io serverless Node.js backend platform.
Key takeaways:
- Integration with Mailgun can be easy and fun
- Serverless APIs, jobs and queues is a powerful combination
- The Codehooks CLI can be used to import CSV data and complement the API
Also check out the documentation to get started using codehooks.io to create your own serverless backend API and microservices.
---
## Build a random quotes API using serverless JavaScript and a NoSQL database
*(Note: This content contains MDX/JSX code)*
API's, Microservices and JavaScript are the digital glue of the modern Internet.
This blog post shows how you can build a fast serverless API for a large data set.
We'll use a 144MB file with 500K famous quotes to create an efficient NoSQL datastore using code hooks.
We'll build and deploy a public serverless API to serve random quotes from the NoSQL database to clients.

As a demonstration of the quotes API we'll create a small React app that displays a slideshow with famous quotes and authors. The finished React demo app looks something like the screen shot shown below.

You can check out the live demo app [here](https://restdb.github.io/codehooks-io-react-quotes/).
## Download 500K quotes from kaggle.com
[Kaggle](https://kaggle.com) is a fantastic source for learning and experimenting with data and insights.
Not surprisingly, they have a large dataset with 500K quotes from famous people.
Link to dataset: https://www.kaggle.com/datasets/manann/quotes-500k
The dataset is a comma separated (CSV) file format and contains three columns: the quote, the author of the quote and the category tags for that quote.
The snippet below shows the raw file content:
```csv
quote,author,category
"I'm selfish, impatient and a little insecure. I make mistakes, I am out of control and at times hard to handle. But if you can't handle me at my worst, then you sure as hell don't deserve me at my best.",Marilyn Monroe,"attributed-no-source, best, life, love, mistakes, out-of-control, truth, worst"
"You've gotta dance like there's nobody watching,Love like you'll never be hurt,Sing like there's nobody listening,And live like it's heaven on earth.",William W. Purkey,"dance, heaven, hurt, inspirational, life, love, sing"
You know you're in love when you can't fall asleep because reality is finally better than your dreams.,Dr. Seuss,"attributed-no-source, dreams, love, reality, sleep"
...
```
We'll download the complete dataset to a local 144MB file `quotes.csv` that we'll import to our NoSQL datastore later.
But before we can start to build our quotes application, we need a new project for the source code and stuff.
## The server (less)
### Create a new Codehooks project with a skeleton API
To host our serverless API and data we create a new Codehooks project. If you're new to Codehooks.io you can read more about [getting started](/docs/) in the docs.
From the command line using the [CLI](/docs/cli) we'll create a new project with the `coho create` command:
```bash
coho create quotes
cd quotes
```
In the `index.js` source file we add the following code for our first skeleton API.
```js
/*
* Random quotes API. Codehooks (c) example.
*/
import app from 'codehooks-js';
// routehook function
function randomQuote(req, res) {
console.log('Quote coming up soon');
res.json({
quote: 'The true sign of intelligence is not knowledge but imagination.',
author: 'Albert Einstein',
});
}
// REST hook
app.get('/quote', randomQuote);
// bind to serverless runtime
export default app.init();
```
Before we can deploy our application we also need to add dependent NPM packages.
```bash
npm i codehooks-js -s
```
Next we can deploy the serverless API to the Codehooks cloud.
```bash
coho deploy
```
```bash title="Output of the deploy command"
Project: quotes-q04p Space: dev
Deployed Codehook successfully! π
```
To access the API from clients we need to create a read only API key.
```bash
coho add-token --readonly
Created new token 121a4850-549e-4be1-9b1e-61d8c6ef7f21 with access READ
```
```bash title="Testing the API with curl"
curl -X GET \
'https://quotes-q04p.api.codehooks.io/dev/quote' \
--header 'x-apikey: 121a4850-549e-4be1-9b1e-61d8c6ef7f21'
```
```json title="Output from the curl test"
{
"quote": "The true sign of intelligence is not knowledge but imagination.",
"author": "Albert Einstein"
}
```
Awesome, our skeleton API works, next we can continue to create the real quotes API, and finally our React app.
:::info Github repo
[Check out the full source code for the serverless API at GitHub.](https://github.com/RestDB/codehooks-io-examples/tree/main/quotes)
:::
### Import the dataset csv to a data collection in the NoSQL datastore
The CLI command `coho import` can import CSV and JSON files to create data collections in the NoSQL datastore. Notice that we've added the `--rowcount` parameter, this injects an incremental numeric field `rowcount` for each object imported in the datastore. This will be used later to create a search index for fast lookups and finally the random quotes query.
```bash
coho import ./quotes.csv quotes --rowcount
Finished import of 499709 objects
```
:::tip
Please notice that you can experiment on the free plan with a smaller version of the dataset in the GitHub source [here](https://github.com/RestDB/codehooks-io-examples/tree/main/quotes/quotes.csv).
:::
### Check that we got all 500K quotes
From the import command we saw that the output reported 499709 objects.
We can also double check that the total number of objects in the datastore is correct using the `coho count` CLI command.
```bash
coho count quotes
{ count: 499709 }
```
Cool, the total count shows 499709, which is correct.
Using the `coho query` command we can also execute a test query to fetch a small slice of e.g. 2 records skipping 250K records into the dataset.
```bash
coho query quotes --limit 2 --offset 250000 --pretty
```
The output below shows that the csv file has been successfully imported and converted to a NoSQL data collection.
```js
{
quote: 'I swear that while I live I will do what little I can to preserve and to augment the liberties of man, woman, and ',
author: 'Robert G. Ingersoll, The Liberty of Man, Woman and Child',
category: 'equality, freedom, honesty, justice, liberty, mercy, rights, virtue',
rowcount: 250041,
_id: '183cff13185-08fcnc76koz5ns'
}
{
quote: 'It was possible to love life, without loving your life.',
author: 'Christopher Coe',
category: 'gratitude, honesty, life',
rowcount: 250045,
_id: '183cff13185-0czojrvb5heddj'
}
```
Yay, the quotes data collection looks great, we're all good to continue π
### Add index for fast lookup
Performing operations on large datasets can slow down any application, we don't want that. In our case, we need to fetch a random object from the `quotes` dataset that has almost 500K objects.
Indexing is a powerful mechanism that creates fast lookups for a given key that exists in the indexed key space. As you will see in the real API code in the next section, the indexed `rowcount` field will be used to fetch our random quote - fast. Use the [`coho createindex`](/docs/cli#createindex) command to create the index for the `rowcount field`.
```bash
coho createindex --collection quotes --index rowcount
```
### Create the API for random quotes
The code for the final random quotes API is shown below. Notice the logic that uses a random number between 0-499709, i.e. total number of quotes in our dataset. By using the index field `rowcount` it's super fast to fetch an object directly from the database and return a random quote.
```js
/*
* Random quotes API
* Codehooks (c) example.
*/
import app from 'codehooks-js';
// routehook function
async function randomQuote(req, res) {
// pick a random index number to fetch by the rowcount index field
const randomPos = Math.floor(Math.random() * 499709) + 1;
// open the NoSql Datastore
const conn = await Datastore.open();
// tell engine to use index field rowcount, and just start and end at the same pos
const options = {
useIndex: 'rowcount',
startIndex: randomPos,
endIndex: randomPos,
};
// run the query, returns a stream of 1 item ;)
conn.getMany('quotes', options).on('data', (data) => {
const { quote, author } = data;
res.json({ quote, author });
});
}
// REST hook
app.get('/quote', randomQuote);
// bind to serverless runtime
export default app.init();
```
Deploy the API again with `coho deploy`.
With the real API in place, we can move on to create a simple React app to integrate with our quotes API.
:::tip
[Postman](https://www.postman.com/api-platform/api-testing/), [Thunder](https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client) or [curl](https://curl.se) are great tools for API development and testing.
:::
## The client
### Creating the quotes React app
We can use the brilliant [create-react-app](https://create-react-app.dev/docs/getting-started/) utility to kick-start the development of our quotes React app.
And we've deployed the app to GitHub pages by following this tutorial [https://github.com/gitname/react-gh-pages](https://github.com/gitname/react-gh-pages).
Start with the initial `create-react-app` command and go with the flow, it could not be any easier.
```bash
npx create-react-app react-quotes
```
Check out the complete source code for the React quotes app at GitHub [https://github.com/RestDB/codehooks-io-react-quotes](https://github.com/RestDB/codehooks-io-react-quotes).
The `App.js` source file contains the main display logic. It's really simple and does little more than to render the quote and the author from the state object. Also check out the `App.css` for the unimpressive app design π€¦π½ββοΈ
```js
render() {
const { advice } = this.state;
return (
);
}
```
The state object is populated from the API call using the built in `fetch` API.
```js
fetchQuote = async () => {
try {
const resp = await fetch('https://quotes-q04p.api.codehooks.io/dev/quote', {
method: 'GET',
headers: { 'x-apikey': API_KEY },
});
const { quote, author } = await resp.json();
this.setState({ quote, author });
} catch (ex) {
console.error(ex);
}
};
```
You can test the app locally with the `npm start` command.
This starts a local server at `http://localhost:3000` that loads the React quotes app in your local browser.

**Source code**: [https://github.com/RestDB/codehooks-io-react-quotes](https://github.com/RestDB/codehooks-io-react-quotes).
**Live demo here**: [https://restdb.github.io/codehooks-io-react-quotes/](https://restdb.github.io/codehooks-io-react-quotes/).
## Summary
API's and microservice architecture plays a fundamental role in modern application development.
In this blog post we've shown how the Codehooks serverless architecture can act as a complete backend and API for a React app.
Key takeaways:
- Kaggle.com has some very useful datasets
- Large datasets are easily imported to the NoSQL datastore using the CLI
- Fast development and deployment of your API
- Database indexes speeds up your API
- Deploying your React app to GitHub pages is really cool
Thank's for reading this, and I hope you've learned something new and useful.
> **"Einstein may have discovered the theory of relativity - Chuck Norris invented the reality of kicking ass."**
---
## How to authenticate a React app against a serverless backend API with Auth0
*(Note: This content contains MDX/JSX code)*
Most web applications and mobile apps need a way for users to authenticate themselves so that the backend APIs and database can access data in the context of the user. One of the leading service providers within authentication services is Okta [Auth0](https://auth0.com). They provide a very powerful platform and a ton of features for creating optimal user login experiences.
In this blogpost, we'll show how you can easily authenticate codehooks.io serverless API routes using only a few lines of code together with the public signing key/JWKS endpoint method of validating JWT tokens. Don't know what that is? Don't worry. Read on π. We have used a [React single-page example](https://github.com/auth0-samples/auth0-react-samples/tree/master/Sample-01) from Auth0 here, but it should work just fine with their other examples as well (Angular, JavaScript, Vue).

We'll go through these steps in this blogpost:
- Sign up / fetch the demo application from Auth0
- Create and set up a codehooks.io space which contains the database and serverless code
- Add an authentication middleware function to capture and make user information available to all serverless API routes
- Modify the Auth0 demo application to use codehooks.io as an authenticated backend
- Walk through of the [demo application](https://restdb.github.io/codehooks-io-auth0demo/)
### Sign up / fetch the demo application from Auth0
To get started you first need to [sign up with Auth0](https://auth0.com). The next step is to create an Auth0 application. Choose the "Single Page Web Applications" type as shown in the image below.

Choose your favorite web application front-end framework. We chose React for this blog post, but it should not matter.

Download the pre-configured sample code by clicking the "Download sample" button. By following the steps presented by Auth0, you should now have an application where you can sign up and log in and perform an authenticated call to a local Node.js Express server provided in the example.

The frontend is good for now, let's create a codehooks backend to replace the local one!
### Create a Codehooks serverless backend API with the database and serverless code
To host our authenticated serverless API we create a new Codehooks project. If you're new to Codehooks.io you can read more about [getting started](http://localhost:3000/docs/) in the docs.
From the command line using the [CLI](http://localhost:3000/docs/cli) we create a new project with the `coho create` command:
```bash
coho create mybackend
cd mybackend
```
:::info DEV space is auto-created
When we create a new project, codehooks automatically adds a "dev" space which consists of code + database + settings.
:::
Before we continue with adding and deploying the backend code, we need to set up authentication.
### Setting up our serverless backend authentication with a JWKS URL
All codehooks.io spaces can authenticate API routes by simply setting a special JWKS URL endpoint. JWT tokens in incoming requests will then be verified using a public key from this endpoint.
To set this endpoint for a space/datastore, we can use the CLI (docs) or the admin UI as shown below. When a JWKS endpoint is set for a space, Codehooks will check ALL routes for a valid JWT token and try to validate it using this endpoint.

In the screenshot above, you see that we also have set an environment variable AUTH0_DOMAIN. We are going to access this from our serverless code like this `process.env.AUTH0_DOMAIN`.
You can find the name of the domain under "Settings/Basic Information" of your application in Auth0 or in the downloaded React example in the file `auth_config.json`.
For our example, it looks like this:
```js
{
"domain": "dev-sh8rro2f.us.auth0.com",
"clientId": "QXAzwX8IXGtcK80ivCVxSFNHoQiNDiL0",
"audience": "https://codehooksbackend"
}
```
In Auth0, the JWKS endpoint is just "https://" + domain + "/.well-known/jwks.json.
### Adding and deploying code for the authenticated serverless backend
Replace the contents of the `index.js` source file generated from the `coho create mybackend` command with the following code.
```js
/*
* Codehooks code for fetching user info for all routes using Auth0 authentication
*/
import { app, Datastore } from 'codehooks-js';
import fetch from 'node-fetch';
// middleware to get Auth0 user info in request
// all routes will now have access to user info in req.user
const userProfileFromAuth0 = async (req, res, next) => {
try {
const { authorization } = req.headers;
if (authorization) {
const token = authorization.replace('Bearer ', '');
const conn = await Datastore.open();
// try to load user from codehooks.io key/value store
const user = await conn.get(`token-${token}`, { keyspace: 'sessions' });
// found in cache?
if (user) {
req.user = JSON.parse(user);
return next();
}
// fetch user from Auth0 API
const resp = await fetch(`https://${process.env.AUTH0_DOMAIN}/userinfo`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
req.user = await resp.json();
// store in key/value store for twenty minutes
conn.set(`token-${token}`, JSON.stringify(req.user), {
keyspace: 'sessions',
ttl: 1000 * 60 * 20,
}); // ttl twenty minutes
} else {
return res.sendStatus(401);
}
next();
} catch (error) {
next(error);
}
};
// intercept all routes with the user profile middleware function
app.use(userProfileFromAuth0);
// our API endpoint for the demo
app.get('/hello', async (req, res) => {
const nickname = (req.user && req.user.nickname) || 'anonymous';
const conn = await Datastore.open();
const apicounter = await conn.incr('apicounter', 1); // increase a counter for each call
res.json({
message: `Hello ${nickname}`,
user: req.user,
now: new Date(),
apicounter,
});
});
// bind to serverless runtime
export default app.init();
```
The middleware function `userProfileFromAuth0` fetches a users' profile from Auth0 and caches it to prevent extra calls to the their API. Since all codehooks spaces have a key/value datastore, it's quite easy and convenient to use this for caching in a serverless environment where caching in memory is not possible. The profile is then set as a new "user" property of the request (req.user), making it available to all downstream routes. As shown, we access it in the `/hello` route to return the profile and a custom message (`Hello ${nickname}`).
As we mentioned above, all routes in this project are already authenticated before it reaches our API because we've set the JWKS endpoint.
Before we can deploy our application we also need to add dependent NPM packages.
```bash
npm i codehooks-js -s
```
Next we can deploy the serverless API to the Codehooks cloud.
```bash
coho deploy
```
```bash title="Output of the deploy command"
Project: mybackend-6708 Space: dev
Deployed Codehook successfully! π
```
### Modifying the JavaScript / React client code
In the React sample application we downloaded from Auth0, we need to replace a few lines in the `ExternalApi.js` file (in addition to adding some useful text and relabelling of a button and a menu).
```js
const { apiOrigin = 'http://localhost:3001', audience } = getConfig();
```
is replaced by:
```js
const { apiOrigin = 'https://mybackend-6708.api.codehooks.io', audience } =
getConfig();
```
and
```js
const response = await fetch(`${apiOrigin}/api/external`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
```
is replaced by:
```js
const response = await fetch(`${apiOrigin}/dev/hello`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
```
### The Auth0 React demo application
Let's quickly walk through the demo application in case you don't want to [test it yourself π](https://restdb.github.io/codehooks-io-auth0demo/)
The start page, which we deployed for free using [Github Pages](https://pages.github.com) looks like this:

You can now sign up and log in.

You should now see your own avatar in the top right menu and a menu option saying "External codehooks.io API call".

When you click this menu, you'll end up here:

Clicking the "Ping codehooks.io API" button should show you something like the image below. We see the result of our API call to our authenticated `/dev/hello` endpoint of our codehooks.io space.

You can test the demo application yourself [here](https://restdb.github.io/codehooks-io-auth0demo/). Sign up and log in with a social account or use email/password.
:::info Github repo
You can find the source for the React client and codehooks.io serverless database backend here on [GitHub.](https://github.com/RestDB/codehooks-io-auth0demo)
:::
# Summary
We've demonstrated how to authenticate a JavaScript / React app against a serverless backend API using the flexible [Auth0](https://auth0.com) authentication service.
Key takeaways:
- Auth0 is easy to use and provides working sample applications for Angular, React, Vue JS and plain JavaScript.
- Verification of JWT tokens using public JWKS (RS256) can be set up with a simple URL on codehooks.io.
- A simple middleware function can provide a user context for all codehooks serverless API routes.
- A user context object available for all API calls makes it easy to create APIs which which store and fetch user specific data from the built-in codehooks.io database or key/value storage (or any other source).
- You can copy this middleware function and use in your own projects as is.
---
## How to quickly create a CRUD REST API backend using codehooks.io - with code example
*(Note: This content contains MDX/JSX code)*
Create, read, update, and delete operations, often popularly known as [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) β are essential in any application interacting with a database. In the schemaless and document oriented [NoSQL](https://en.wikipedia.org/wiki/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](https://en.wikipedia.org/wiki/Representational_state_transfer) with CRUD operations and validation for your application using codehooks.io.

## 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](/docs/) in the docs.
Use the [CLI command `coho create`](/docs/cli) to create your project, e.g.:
```bash
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](/docs/cli#info).
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 π):
```js
...
// 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](https://www.npmjs.com/package/yup) and [JSON schema](https://www.npmjs.com/package/ajv).
## 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](https://www.npmjs.com/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.
```js title="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](https://www.postman.com/downloads/) 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.

The equivalent curl REST API call example is shown below:
```bash
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:
```js
...
// 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:
```js
{
"name": "Jill James"
}
```
Will return a status code `400` with this error message/object:
```js
{
"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.
| Verb | Codehooks.io route | Description |
| :------- | ----------------------------------------------------------------------------------- | ------------------------------------- |
| `GET` | https://<projectid>.api.codehooks.io/:space/\:collection[?field=value&\.\.\.] | Retrieve all objects or use API query |
| `GET` | https://<projectid>.api.codehooks.io/:space/\:collection/:ID | Retrieve object by ID |
| `POST` | https://<projectid>.api.codehooks.io/:space/\:collection | Add object |
| `PUT` | https://<projectid>.api.codehooks.io/:space/\:collection/:ID | Replace object by ID |
| `PATCH` | https://<projectid>.api.codehooks.io/:space/\:collection/:ID | Update object by ID |
| `PATCH` | https://<projectid>.api.codehooks.io/:space/\:collection/\_byquery | Update object(s) by query |
| `DELETE` | https://<projectid>.api.codehooks.io/:space/\:collection/:ID | Delete object by ID |
| `DELETE` | https://<projectid>.api.codehooks.io/:space/\:collection/\_byquery | Delete 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`.
:::tip Use your favorite schema provider π₯
`codehooks-js/crudlify` supports these popular data and validation schemas:
- [Yup](https://www.npmjs.com/package/yup) - Dead simple Object schema validation
- [Zod](https://www.npmjs.com/package/zod) - TypeScript-first schema validation with static type inference
- [JSON.schema](https://www.npmjs.com/package/ajv) - 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](https://www.npmjs.com/package/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:
```url
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:
```url
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:
```js
...
// 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:
```js
{
"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:
```js
{
"name": "Bill Roads",
"status": "open",
"products": [
{
"name": "milk",
"price": 1.2
},
{
"name": "juice",
"price": 3
}
],
"balance": 4.2,
"_id": "1849588d162-n0upyc0ec8y1po"
}
```
:::tip 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.
```js title="Event hooks format"
hooks.before(, handlerFunction)
hooks.after(, handlerFunction)
```
For example:
```js
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:
- codehooks.io authentication [documentation](/docs/authentication)
- In this blog post about [how to authenticate a React app](/blog/how-to-authenticate-a-react-app-against-a-serverless-backend-api-with-auth0)
## 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](https://www.npmjs.com/package/codehooks-js) library in combination with the simple deployment of codehooks.io.
We also recommend taking a closer look at the excellent [Yup schema builder](https://github.com/jquense/yup). 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.](https://www.npmjs.com/package/codehooks-js) supports this too.
Look out for Swagger / OpenAPI support in the near future!
:::info Github
The full source code for this example is avaliable [here](https://github.com/RestDB/codehooks-io-examples/tree/main/crud).
:::
---
## What is JAMstack - an introduction
What is JAMstack about? The JAMstack architecture is gaining popularity among frontend developers for its ability to provide a modern, fast, and secure web development experience.
In order to achieve better performance, developers have had to look at different ways and tools that together could increase speed and at the same time maintain a high level of security. Traditional and dynamic CMS-based websites have the disadvantage that data must be retrieved from a database or similar in order to deliver content on the websites. Using so-called reverse proxies and caches (i.e., "fast storage") such as Varnish and nginx, was one of the few options to increase performance. That is, until the JAMstack architecture (and frameworks) arrived.

# What is JAMstack?
At its core, the JAMstack is a web development approach that focuses on using client-side JavaScript, reusable APIs, and prebuilt Markup to create web applications. This architecture allows for faster and more secure web development by using prebuilt and pre-rendered pages, which are then delivered to the user via a content delivery network (CDN).
The [JAMstack.org](https://JAMstack.org) website defines the term as:
> "Jamstack is an architectural approach that decouples the web experience layer from data and business logic, improving flexibility, scalability, performance, and maintainability. JAMstack removes the need for business logic to dictate the web experience. It enables a composable architecture for the web where custom logic and 3rd party services are consumed through APIs."
JAMstack, as an architectural concept, was created by Mathias Biilmann Christensen, co-founder of Netlify - a rapidly growing Cloud Computing company offering hosting and serverless backend services for static websites. It can be used to build static websites that are significantly faster and more stable than dynamic ones, but are still dynamic in the sense that the web pages are updated when new content is published or the source code changes. JAMstack is thus not a specific tool, but an architecture and ecosystem that makes it efficient to create lightning-fast websites.
# What does JAMstack mean?
JAM stands for "Javascript", "APIs" and "Markup":
- **Javascript**Β handles all parts of the website that should be dynamic once the web pages are first loaded. Modern front-end frameworks such as VueJS, React or Angular are well suited to use for this.
- **APIs** are required because a JAMstack app/website does not have a server. This means that no self-developed "backend" code is necessarily needed in this architecture. Developers can focus on implementing front-end logic. If you need to store user input, process images, authenticate users, perform content search, transform images, handle payment and the like, this can be handled with external services via standardized or custom APIs.
- **Markup** is the HTML code that is generated in the build step. This code is pure static HTML, but it can be dynamic in the sense that the HTML is built from data retrieved from APIs combined with HTML templates. In many frameworks, Javascript (React, Vue) can also be used to create this static part. So-called headless CMS APIs are often used for the dynamic content. In such a setup, the construction of markup will be initiated when the source code is checked into version control (such as Github) or by web hooks triggered when the CMS API receives new or updated content.
In the illustration of the JAMstack architecture below, we see that the combination ofΒ templatesΒ andΒ contentΒ is compiled into static HTML pages/content which is then distributed to a Content Network (CDN). The CDN helps ensure that the pages are loaded fast and as close to the user as possible.

# Why should you start using JAMstack?
JAMstack is a modern and efficient way to develop websites. Compared to centralized, database-dependent websites, JAMstack-based websites have the following advantages:
- Speed - Pages load lightning fast
- Lower cost - static websites are much cheaper compared to dedicated servers/databases
- Scalability - content is static and not loaded from a central server
- Maintenance - there is no need to monitor any servers
- Security - there are no secrets on a static website - only access to APIs needs to be secured
# SEO - Google favors JAM
From an SEO perspective, there are few reasons to use anything other than JAMstack for content-rich sites. Google favors websites that load quickly and users don't click the "back button" to the same extent. From the developer's point of view, JAMstack will provide a better experience as they can focus on developing the best user experience, use the latest technology and worry less about performance and scalability.
# Developers love it
One of the key benefits of the JAMstack for frontend developers is the ability to focus on building user interfaces without the need for server-side rendering or complex backend logic. This allows for a more agile and efficient development process, as well as the ability to leverage a wide range of third-party APIs and services to build dynamic and engaging user experiences. They can use their favorite UI frameworks (Like React, Vue, Svelte). If there is a need for a custom backend API (other than a headless CMS), there are plenty of tools which makes this relatively simple like for example [Heroku](https://heroku.com), [Firebase](https://firebase.google.com/), [Xata](https://xata.io), [restdb.io](https://restdb.io) and [codehooks.io](https://codehooks.io) π
# Popular frameworks
You'll find the most popular generators for JAMstack Sites on the [JAMstack website](https://jamstack.org/generators/). These are the top contenders:
- [Next.js](https://nextjs.org) - very popular in the React community - supports static site generation and server-side-rendered (SSR) dynamic applications.
- [Hugo](https://gohugo.io) - Insanely fast builds and many features - optimized for speed, ease of use and configurability. Built on the Go programming language.
- [Gatsby](https://gatsbyjs.org) - React based with a huge ecosystem of content/data plugins - uses GraphQL to provide content/data.
- [Nuxt](https://nuxtjs.org) - the Vue alternative to NextJS - supports both static site generation and SSR.
- [Docusaurus](https://docusaurus.io) - Use React and Markdown to create powerful static sites for documentation or whatever (this blog uses Docusaurus).
# Summary and conclusion
In short, the JAMstack is a powerful and efficient web development architecture that is ideal for frontend developers looking to build modern, fast, and secure web applications. With its focus on client-side JavaScript, reusable APIs, and prebuilt Markup, the JAMstack offers a range of benefits that make it an attractive option for frontend developers looking to create engaging and dynamic user experiences.
When someone ask you 'What is JAMStack?', you should hopefully now be able to provide a decent answer π
---
## How to create a Node.js REST API backend using ChatGPT and codehooks.io
*(Note: This content contains MDX/JSX code)*
[ChatGPT](https://openai.com/blog/chatgpt/) and other advanced AI language models have become an integral part of the modern development workflow. From GPT-4 to Claude and other competitors, these AI assistants have transformed how we approach coding and content creation. And we understand why! These AI models have proven themselves invaluable for producing all kinds of text and code, helping developers work more efficiently and creatively than ever before.
While modern AI models are now well-versed in a wide range of technologies and frameworks, we've found that providing concrete examples still leads to the best results. This approach helps the AI understand exactly how we want to use codehooks.io's specific features and conventions, ensuring the generated code follows our best practices.
By providing ChatGPT with examples of [codehooks.io](https://codehooks.io) backend code, we will in this blog post show you how you can use ChatGPT to produce codehooks JavaScript backend code that is both functionally correct, easy to understand and instantly deployable. In the end, we will have a tool that can help us quickly prototype and build different kinds of backends with minimal effort. So, let's get started!

:::tip Update 2025-03-20
We have now created a dedicated [ChatGPT prompt](/docs/chatgpt-backend-api-prompt) you can use to generate codehooks.io backend code with ChatGPT.
:::
## A database-powered CRUD REST API with ChatGPT
We thought that ChatGPT could be a good match for creating code for codehooks.io due to codehooks' serverless and "instant deploy without setup" capability along with its built-in database, jobs and worker queue features.
### How to ask ChatGPT for a favor π
If you haven't [signed up for ChatGPT](https://chat.openai.com/auth/login) yet, you must do so before you can talk to it. To create our question for creating a CRUD REST API for deployment with codehooks.io using the [codehooks.js/crudlify](https://www.npmjs.com/package/codehooks-js) NPM package, we copied the following code from one of our [blog posts](/blog/how-to-quickly-create-a-crud-rest-api-backend-using-codehooks-io-with-code-example#a-minimal-crud-api-code-example):
```js
/*
* Minimal CRUD API code example using Yup schema
*/
import { app } 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
app.crudlify({ product });
// bind to serverless runtime
export default app.init();
```
:::tip Create database-backed REST APIs fast with crudlify β‘οΈ
When you need a "standard" REST API with NoSQL database support, using the library 'codehooks.js/crudlify' must be one of the simplest ways to do it with code. The library generates the common REST API routes: GET, POST, PUT, PATCH and DELETE and persists the data in the codehooks.io NoSQL database. You can also query the data quite easily.
:::
Then we added the following text before this code: "This is an example of codehooks.io backend code:", and we added this after the code: " Can you create a codehooks.io backend like this with a more complete example for an online shop."

And voilΓ‘! This is the code we got back from ChatGPT:
```js
import { app, crudlify } from 'codehooks-js';
import * as yup from 'yup';
// Define the schema for the "Product" collection
const productSchema = yup.object().shape({
name: yup.string().required(),
description: yup.string(),
price: yup.number().required(),
image: yup.string().url(),
category: yup.string().required(),
ratings: yup.array().of(yup.number()),
});
// Define the schema for the "Order" collection
const orderSchema = yup.object().shape({
customerName: yup.string().required(),
customerEmail: yup.string().email().required(),
products: yup.array().of(
yup.object().shape({
productId: yup.string().required(),
quantity: yup.number().required(),
})
),
total: yup.number().required(),
status: yup.string().oneOf(['pending', 'shipped', 'delivered']),
});
// Add CRUD routes for the "Product" and "Order" collections with the corresponding schemas
crudlify(app, { products: productSchema, orders: orderSchema });
// Bind the app to the serverless runtime
export default app.init();
```
This was the explanation ChatGPT was so kind to provide:
> This code creates two collections for Product and Order, defined by the productSchema and orderSchema respectively. The schema of product includes name, description, price, image, category and ratings. The schema of order includes customerName, customerEmail, products, total, status.It also demonstrates the use of different validation techniques like required, email, url and oneOf() for various fields in the collection.
> It can be more fine-tuned based on the specific needs of the backend.
> Please note that this is just an example to give you an idea of how you can define your collections and schemas, and you may need to adjust it to fit the specific requirements of your application.
### Deploying the AI-generated JavaScript code without touching it π¬
This code certainly looks correct at first glance. I have, however, several times caught ChatGPT "inventing" totally new libraries and methods and got an excuse afterwards when I pinpointed it π
Let's deploy the code! If you want to do this yourself, follow the instructions in our [Quick Start](/docs) to get ready. It only takes a few seconds.
First, create a new project:
```bash
codehooks create shopbackend
```
This [CLI command](/docs/cli) creates a codehooks project, adds a folder named "shopbackend" and initializes a "hello world" index.js JavaScript file.
Replace the contents of the "index.js" file with ChatGPTs generated code above and save it.
You also need to install the necessary npm packages:
```bash
npm i codehooks-js codehooks-js yup
```
In the same folder as index.js, run the deploy command:
```bash
codehooks deploy
```
Result:
```bash
Project: shopbackend-h3r5 Space: dev
Deployed Codehook successfully! π
```
Yes! π
It worked...or rather it compiled and was deployed correctly by codehooks.io.
Let's test the backend by adding some products using cURL (we also recommend [Postman](https://postman.com)).
:::tip Postman Import button
Using the "Import" button in Postman, you can directly import cURL commands.
:::
This 'info' command is very useful when we need more information about our space AND need some cURL examples:
```bash
codehooks info --examples
```
This is what we get:
```bash
Project name: shopbackend-h3r5
Team: Martin Tornes (personal account)
API endpoint: https://shopbackend-h3r5.api.codehooks.io/dev
Examples in cURL:
curl https://shopbackend-h3r5.api.codehooks.io/dev/myroute -H 'x-apikey: 27b3822e-ecf3-4263-bcee-24acd6aed2d0' -H 'Content-Type: application/json'
curl --request POST https://shopbackend-h3r5.api.codehooks.io/dev/myroute -H 'x-apikey: 27b3822e-ecf3-4263-bcee-24acd6aed2d0' -H 'Content-Type: application/json' --data-raw '{"name": "Mr. Code Hooks", "active": true }'
Spaces:
ββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββ¬βββββββ¬βββββββ
β Name β Tokens β Allowed Hosts β Jwks β Env β
ββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββΌβββββββΌβββββββ€
β dev (active) β 27b3822e-ecf3-4263-bcee-24acd6aed2d0 (RW) β β β β
ββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββ΄βββββββ΄βββββββ
Project members:
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ¬ββββββββ
β name β email β role β
ββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββΌββββββββ€
β Martin Tornes β martintornesdev@gmail.com β ADMIN β
ββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββ΄ββββββββ
```
Let's modify the POST cUrl to add some product data. We'll add a JSON structure with at least the fields ChatGPT marked as required:
```bash
curl --request POST https://shopbackend-h3r5.api.codehooks.io/dev/products \
-H 'x-apikey: 27b3822e-ecf3-4263-bcee-24acd6aed2d0' \
-H 'Content-Type: application/json' \
--data-raw '{"name": "Codehook", "price": 0, "category": "TOOLS" }'
```
Result:
```bash
{"name":"Codehook","price":0,"category":"TOOLS","_id":"185a3157a34-u96ow26u9zcxd0"}
```
It worked! You can now use your preferred programming language and library to interact with this REST API and database.
## Conclusion
codehooks.io is a good fit for getting a lot of help from ChatGPT, you just need to show it an example before you ask a question. The way we demonstrated it here works fine, but there are probably multiple ways to get the most out of it.
We also asked it to create a scheduled "CRON" job using codehooks.io job "hooks", which should Tweet each afternoon at 5pm. It produced working code using Twitter's library and the correct codehooks.io JavaScript code. Ready to deploy!
We could have asked it to do more to elaborate on the code it created. This type of dialog is in fact the recommended way to work with it to produce the best results. We'll leave that to a later blogpost or a whole dedicated section where we add ChatGPT examples.
ChatGPT is a glimpse into an exciting future of AI-assisted programming. It's not exactly NoCode, but it surely is automated and saves a ton of time.
This blog post was not written by ChatGPT (maybe it could have been π)
:::tip
Check out our [example](/docs/examples/chat-gpt-rest-api-nodejs-example) of how to use the Open AI API with ChatGPT to build your own easy to use API.
:::
---
## Simplify Your Codehooks Development with Devcontainers
*(Note: This content contains MDX/JSX code)*
In this blog post, we'll show you how to set up and use a devcontainer for Codehooks development. Devcontainers, a feature of Visual Studio Code, allow you to create a consistent development environment that can be shared across your team, streamlining your workflow and reducing setup time. Developers on OS X, Windows and Linux will now get the same environment with everything set up correctly from the start.
Codehooks is a powerful Backend-as-a-Service platform that provides Express-like endpoints, a NoSQL document database, a key-value store, a worker queue system, and a CRON-like job scheduling system, all managed through a simple CLI. To set up a devcontainer for Codehooks, follow the steps outlined in this article.

### Step 1: Install Prerequisites
Before starting, ensure you have the following installed on your system:
- Visual Studio Code
- Docker
- Dev Containers extension for Visual Studio Code
### Step 2: Create a New Project
Create a new folder for your project and open it in Visual Studio Code.
### Step 3: Add Devcontainer Configuration Files
Press F1 to open the command palette, type "Dev Containers: Add Dev Container Configuration Files..." and press Enter. Choose a base container like "Node.js" to start with.
### Step 4: Configure the Devcontainer
In the newly created .devcontainer folder, open the devcontainer.json file and add the necessary configurations:
```json
// See https://containers.dev/implementors/json_reference/ for configuration reference
{
"name": "Codehooks Devcontainer",
"build": {
"dockerfile": "Dockerfile"
},
"remoteUser": "node",
"forwardPorts": [3000],
"postCreateCommand": "sudo npm install -g codehooks"
}
```
This configuration installs the required extensions, forwards port 3000, and installs the Codehooks CLI globally after the container is created.
### Step 5: Build and Run the Devcontainer
Close the current window, and reopen the project folder. Visual Studio Code should prompt you to reopen the folder in the container. Click "Reopen in Container."
### Step 6: Use the Codehooks CLI
After the container is built and running, open the terminal in Visual Studio Code. You can now use the Codehooks CLI to manage your project. You can now go to the ['Quick start'](/docs) to start developing with codehooks.io (and you can skip the first step where you install the CLI π€ )
## Conclusion
With this setup, you can develop your application using the Codehooks platform within the devcontainer environment. The extensions, port forwarding, and post-create command specified in the devcontainer.json file help to create a seamless development experience. Devcontainers provide a consistent and efficient way to streamline your Codehooks development workflow, making it easier to collaborate with your team and manage your projects.
There is **a lot** more you can do with devcontainers. We have only created one of the simplest setups you can have. Check out [**containers.dev**](https://containers.dev/) to learn more.
---
## React Todo example App with secure NoSQL API using Auth0.com
*(Note: This content contains MDX/JSX code)*
import shot1 from './img/screenshot1.png';
import shot2 from './img/login.png';
import shot3 from './img/auth0.png';
import appdesign from './img/appdesign.png';
In this blog post, we'll show how to develop and deploy a full-stack React Todo App with user authentication and secure database REST APIs.

## Learning by (to)doing
The concept of a Todo App is a popular way of learning how a specific technology stack works to solve an intuively understood problem - the ubiquitous _todo list_.
- β» Finish homework
- β Buy milk
- β Call mum
There are countless variations of todo app examples available, each with its own unique features and implementation approaches.
We will provide a full-stack practical example with a [React](https://react.dev/) client Todo App integrated with [Auth0](https://auth0.com) for authentication and backend written using [codehooks.io](https://codehooks.io) (API and NoSQL database).
Our code example is a modified version of an [existing](https://github.com/samgamage/todo-react) but simpler React Todo App written by Samuel Gamage.
The following technologies will be used:
- User authentication and secured REST API by [Auth.com](https://auth0.com)
- Backend NoSQL database and API in Node.js by [codehooks.io](https://codehooks.io)
- UI components using [Material UI](https://mui.com/material-ui/) version 5
- Deployment of the React Todo App to [Github Pages](https://pages.github.com/)
## The finished React Todo App
The finished result of our React Todo App user interface is shown in the screenshot below. Notice the (authenticated) user profile and the following personal todo items fetched dynamically from the database via a REST API.
[Click here to try out the live example](https://restdb.github.io/codehooks-io-react-todo/) of the React Todo App.
[The full source code is available at GitHub here.](https://github.com/RestDB/codehooks-io-react-todo)
Cool, we've seen how the demo application works. Let's dig into the details of how to create this full-stack React Todo App from scratch.
## 3 easy steps to create the React Todo App
The React Todo App is a SPA (Single-Page Application) with 3 major components.
1. User authentication
2. Backend API and database
3. React frontend client application
The rest of this blog post will show you how to complete each step to develop and deploy the complete authenticated full-stack React Todo App.
### Application architecture
The major application components are depicted in the illustration below.
Lets start with the first major building block; adding Auth0 user authentication to the React App.
## Step 1. User authentication
In this example we're using Auth0.com to implement user authentication.
Auth0.com is a leading identity management platform that simplifies the process of adding secure user authentication and authorization to applications.
There are alternatives to Auth0.com. For example [Clerk.com](https://clerk.com) is also a popular identity management platform that provides authentication and authorization solutions for developers. An new open source alternative is [stack-auth.com](https://stack-auth.com) which has many powerful features and integrations.
### Set up an Auth0 account
- Go to the Auth0 website [auth0.com](https://auth0.com) and sign up for a free account.
- Once you're logged in, you'll be taken to the Auth0 Dashboard.
### Create a new Auth0 Application
- In the Auth0 Dashboard, click on the "Applications" tab and then click the "Create Application" button.
- Give your application a name and select the application type as "Single Page Application".
- Under the "Settings" tab for your application, enter the appropriate values for "Allowed Callback URLs", "Allowed Logout URLs" and "Allowed Web Origins." These URLs will depend on your specific application setup.
- Take note of the Application **Domain** and **Client ID**, this will be used in the React App config setting below. The **Client Secret** should not
be shared or distributed in any way.

This completes the necessary setup in the Auth0 Application for now.
The next step is to set up the REST API and database backend for the React App. In the following section, please pay attention to the **JWKS setting** which enables the Auth0 backend integration.
## Step 2. Backend API and database
### Install the Codehooks CLI
The Codehooks.io CLI is used to create and deploy a backend application and API.
In the example React Todo example source code, the backend application is located in the [sub directory](https://github.com/RestDB/codehooks-io-react-todo/tree/main/backend) `/backend` with the two main files `Γ¬ndex.js` and `authHelper.js` which we will discuss in the following.
To get started with Codehooks development we need to install the Codehooks CLI first.
```bash
npm install codehooks -g
```
### Create a new backend application
With the CLI command `coho create` we can create new Codehooks applications.
In this example we'll call our backend application `backend`.
```bash
coho create backend
```
This command creates a default application file `Γ¬ndex.js` and this can be replaced by the two files in the example project `./backend` [directory](https://github.com/RestDB/codehooks-io-react-todo/tree/main/backend).
## Auth0 integration using JWKS
The React Todo App (shown in the step 3 section next) will call the backend application API with an "Authentication" HTTP header containing a JWT token from the Auth0 authentication flow (like this: `Bearer {{insert JWT token}}`).
In order to verify the JWT token for each API call, we add a "JWKS endpoint" from Auth0 which codehooks.io can use. Check out [JSON Web Key Sets (JWKS)](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets) to learn more.
Run the CLI command to set up the correct JWKS endpoint for your Auth0 Application.
```bash
coho jwks 'https:///.well-known/jwks.json'
```
The complete code for the backend Todo Application data API is shown below. The API has simple CRUD operations for listing, creating, updating and deleting Todo items.
```js title=index.js
/*
* React Todo API and NoSql database backend
*/
import { app, Datastore } from 'codehooks-js'; // Standard Codehooks.io lib
import { userProfileFromAuth0 } from './authHelper.js';
// middleware to fetch the user profile into the req.user property
app.use('/todo*', userProfileFromAuth0);
// GET Todo items
app.get('/todo', async (req, res) => {
console.log('Get todo');
const db = await Datastore.open();
db.getMany(
'tododata',
{ owner: req.user.email }, // query Todo items by authenticated user.email
{ sort: { completed: 1, _id: -1 } }
).json(res);
});
// POST a new Todo item
app.post('/todo', async (req, res) => {
console.log('Save todo for', req.user.email, req.body);
const { email: owner } = req.user;
const task = { ...req.body, owner }; // stamp Todo item with user email for data isolation
const db = await Datastore.open();
const result = await db.insertOne('tododata', task);
res.json(result);
});
// PUT Todo item update
app.put('/todo/:id', async (req, res) => {
try {
console.log('Update todo for', req.user.email, req.body);
const task = req.body;
const { id } = req.params;
const db = await Datastore.open();
const result = await db.updateOne('tododata', id, task);
res.json(result);
} catch (error) {
res.status(400).send(error.message);
}
});
// DELETE a Todo item
app.delete('/todo/:id', async (req, res) => {
try {
const { id } = req.params;
console.log('Delete todo for', req.user.email, id);
const db = await Datastore.open();
const result = await db.removeOne('tododata', id);
res.json(result);
} catch (error) {
res.status(400).send(error.message);
}
});
export default app.init(); // Bind functions to the serverless cloud
```
### Get user profile from JWT token
Data for various users are isolated in the database with the `owner/email` property of each item.
In order to get the actual email for an authenticated user (JWT) we can fetch the user profile from a valid JWT token sent in the HTTP headers.
This is implemented as a middleware `app.use('/todo*', userProfileFromAuth0)` used in the main backend Application `index.js` shown above.
Notice how we can cache (20 minutes) the user profile result in the Codehooks key-value database. This avoids flooding Auth0.com with unnecessary requests.
```js title=authHelper.js
import { Datastore } from 'codehooks-js'; // Standard Codehooks.io lib
import fetch from 'node-fetch';
// middleware to get Auth0 user info in request
// all routes will now have access to user info in req.user
// adapt this to your own needs
export const userProfileFromAuth0 = async (req, res, next) => {
try {
const { authorization } = req.headers;
if (authorization) {
const token = authorization.replace('Bearer ', '');
const conn = await Datastore.open();
// try to load user from codehooks.io key/value store
const user = await conn.get(`token-${token}`, { keyspace: 'sessions' });
// found in cache?
if (user) {
req.user = JSON.parse(user);
return next();
}
// fetch user from Auth0 API
const resp = await fetch(`https:///userinfo`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
req.user = await resp.json();
// store in key/value store for twenty minutes
conn.set(`token-${token}`, JSON.stringify(req.user), {
keyspace: 'sessions',
ttl: 1000 * 60 * 20,
}); // ttl twenty minutes
} else {
return res.status(401).send('Missing HTTP header');
}
next();
} catch (error) {
next(error);
}
};
```
### Deploy the backend database API
Before deployment of the database backend we need to install the Codehooks package.
From the backend directory run:
```bash
npm i codehooks-js
```
Now we can deploy the backend by running the Codehooks "deploy" CLI command:
```bash
coho deploy
```
Which should output:
```bash
Project: Space: dev
Deployed Codehook successfully! π
```
:::tip
Run the `coho info --examples` to inspect API tokens and example REST API endpoints.
:::
This concludes the backend for the React Todo App and finally we can start to build the frontend client application.
## Step 3. React frontend client application
### Use `create-react-app` to kick-start the React App
```bash
npx create-react-app todo
cd todo
// start your favorite code editor in the currect directory
code .
```
### Install packages
```bash
npm install @auth0/auth0-react
npm install @mui/material @emotion/react @emotion/styled
npm install @mui/icons-material
npm install gh-pages
```
The rest of the steps inolves replacing index.js and App.js, and add the `./components` directory with the code from the [React Todo App](https://github.com/RestDB/codehooks-io-react-todo).
To save time and kick-start your development you can clone the GitHub repository instead.
```bash
git clone https://github.com/RestDB/codehooks-io-react-todo.git
cd codehooks-io-react-todo
npm install
npm start
```
### Setup config credentials for Auth0 and REST API
Open the the React App `config.json` file and enter your application credentials like shown in the example below:
```json title=config.json
{
"domain": "YOUR_AUTH0_DOMAIN",
"clientId": "YOUR_AUTH0_CLIENT_ID",
"redirect": "/codehooks-io-react-todo",
"apiOrigin": "https://.api.codehooks.io/dev",
"scope": "read:current_user create:current_user_metadata profile email"
}
```
The React Todo App entrypoint code (with Auth0.com integration) is shown in the code example below.
```js title=index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Auth0Provider } from '@auth0/auth0-react';
import config from './config.json';
const domain = config.domain; // YOUR_AUTH0_DOMAIN
const clientId = config.clientId; // YOUR_AUTH0_CLIENTID
const scope = config.scope; // "read:current_user create:current_user_metadata profile email"
// hack to make it work with subdirectory for deployment on github pages
const redirectUri =
window.location.origin.indexOf('localhost') !== -1
? window.location.origin
: `${window.location.origin}${config.redirect}`;
ReactDOM.render(
,
document.getElementById('root')
);
```
## Start the Todo App
To test the React Todo App on your local development machine run:
```bash
npm start
```
Navigate to `http://localhost:3000` and you should see the App login screen similar to the screenshot shown below:
Clicking the **Log In** button opens the Auth0 shield.
### The App.js main logic file
The main logic of the React Todo App is contained in the `App.js` code.
The full code example is shown below.
```js title=App.js
import { Box, Typography, Button, Alert } from '@mui/material';
import React, { useEffect, useState } from 'react';
import './App.css';
import TodoForm from './components/TodoForm';
import TodoList from './components/TodoList';
import { useAuth0 } from '@auth0/auth0-react';
import LogoutButton from './components/LogoutButton';
import Profile from './components/Profile';
import config from './config.json';
const { apiOrigin } = config;
function App() {
const [state, setState] = useState({
todoItems: [],
showResult: false,
apiMessage: '',
error: null,
});
const {
user,
isAuthenticated,
isLoading,
getAccessTokenSilently,
loginWithPopup,
loginWithRedirect,
getAccessTokenWithPopup,
} = useAuth0();
useEffect(() => {
// fires when app component mounts to the DOM
console.log('init useEffect');
callApi().catch((error) => console.error('Init error', error));
}, []);
const callApi = async () => {
try {
const token = await getAccessTokenSilently();
const response = await fetch(`${apiOrigin}/todo`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
console.log('Fetch data', response.status, response.statusText);
let responseData = await response.json();
console.log('Get data', responseData);
responseData = responseData.map((todo) => {
if (todo.completed === undefined) {
todo.completed = false;
}
return todo;
});
if (responseData) {
setState({
...state,
todoItems: responseData,
error: null,
});
}
} catch (error) {
console.error('API error', error);
setState({
...state,
error: error.error || error.message,
});
}
};
const handleLoginAgain = async () => {
try {
await loginWithPopup();
setState({
...state,
error: null,
});
} catch (error) {
setState({
...state,
error: error.error,
});
}
await callApi();
};
const handleConsent = async () => {
try {
await getAccessTokenWithPopup();
setState({
...state,
error: null,
});
} catch (error) {
setState({
...state,
error: error.error,
});
}
await callApi();
};
const handle = (e, fn) => {
e.preventDefault();
fn();
};
async function addTodo(todo) {
// adds new todo to beginning of todos array
const token = await getAccessTokenSilently();
console.log('add', todo);
const response = await fetch(`${apiOrigin}/todo`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'content-type': 'application/json',
},
body: JSON.stringify(todo),
});
console.log('Post data', response.status, response.statusText);
let responseData = await response.json();
console.log('Post data response', responseData);
await callApi();
}
async function toggleComplete(_id) {
// find item
let todoUpdated = state.todoItems.filter((todo) => {
return todo._id === _id;
});
todoUpdated = todoUpdated[0]; // first item
todoUpdated.completed = !todoUpdated.completed;
const token = await getAccessTokenSilently();
console.log('add', todoUpdated);
const response = await fetch(`${apiOrigin}/todo/${_id}`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${token}`,
'content-type': 'application/json',
},
body: JSON.stringify(todoUpdated),
});
console.log('Put data', response.status, response.statusText);
let responseData = await response.json();
console.log('Put data response', responseData);
await callApi();
}
async function removeTodo(_id) {
const token = await getAccessTokenSilently();
console.log('delete', _id);
const response = await fetch(`${apiOrigin}/todo/${_id}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${token}`,
'content-type': 'application/json',
},
});
console.log('Delete data', response.status, response.statusText);
let responseData = await response.json();
console.log('Delete data response', responseData);
await callApi();
}
return (
);
}
export default App;
```
This example App was deployed using Github pages, [read more about it here.](https://pages.github.com/)
## Conclusion
In this blog post, we have demonstrated how to develop and deploy a full-stack React Todo App with user authentication and secure database REST APIs. By following the step-by-step guide, you have learned how to create a practical example using React, Auth0 for authentication, and a backend NoSQL database and API using codehooks.io.
We started by introducing the concept of a Todo App as a popular way to learn and understand a technology stack. We then provided an overview of the technologies used in our example, including React, Auth0, Material UI, and deployment to Github Pages.
The blog post guided you through three easy steps to create the React Todo App. First, we covered user authentication using Auth0. We explained how to set up an Auth0 account, create a new Auth0 application, and obtain the necessary credentials. We also discussed alternative identity management platforms like Clerk.com.
Next, we focused on the backend API and database. We demonstrated how to install the Codehooks CLI and create a new backend application. We explained the integration of Auth0 using JWKS (JSON Web Key Sets) and provided the code for the backend Todo Application's data API.
Finally, we moved on to the React frontend client application. We explained how to kick-start the React App using create-react-app and install the required packages. We provided the code for the entrypoint file, index.js, and the main logic file, App.js. We also discussed how to set up the configuration credentials for Auth0 and the REST API.
Throughout the blog post, we emphasized the importance of user authentication, JWT (JSON Web Tokens), and data isolation. We showcased the finished React Todo App and highlighted its key features.
By following this example, you have gained valuable insights into full-stack development and learned how to integrate user authentication, secure APIs, and a NoSQL database in a React application.
Feel free to explore and build upon the example code provided in this blog post. Use it as a foundation to create more complex applications and enhance your full-stack development skills.
Happy coding! πͺ
---
## Linking Alpine.js to a Database REST API: An Easy Tutorial
*(Note: This content contains MDX/JSX code)*
# Linking Alpine.js to a Database REST API: An Easy Tutorial
In this guide, we'll explore creating a dynamic web application with Alpine.js. We'll set up a frontend using Alpine.js, a minimalistic JavaScript/HTML framework, and integrate it with a comprehensive REST API database backend. For rapid design, we'll use [DaisyUI](https://daisyui.com/) and [Tailwind CSS](https://tailwindcss.com/). This project offers a hands-on way to see these technologies in action.

## Project setup
After creating a [new account](https://account.codehooks.io/login?signup) (if you don't have one already), you should create a new project using the the Codehooks account web app (click the [**NEW PROJECT +**] button). Give your project any name you like, in this example we'll use the projectname **alpine**.

### Log in to your account with the CLI
Make sure to install the latest version of the Codehooks CLI.
```bash
npm i -g codehooks
```
Then, log in to your account.
```bash
coho login
```
### Create the Codehooks source files
Next, create a new directory to hold the source code, e.g. _myalpine_.
```bash
mkdir myalpine
cd myalpine
```
The CLI command `coho init` will attach the local project directory to a Codehooks project (the CLI will present a list), this creates a default Codehooks app file `index.js` and a project specific `config.js` file.
```bash
coho init
```
This command creates a default backend app `index.js`, change this code to the following:
```js
/*
Alpine.js REST API data example
*/
import { app } from 'codehooks-js';
app.static({ route: '/static', directory: '/app' });
// Use Crudlify to create a REST API for any collection
app.crudlify();
// bind to serverless runtime
export default app.init();
```
In same the project directory, install the [codehooks-js](https://www.npmjs.com/package/codehooks-js) NPM package.
```bash
npm i codehooks-js
```
Deploy the default CRUD backend server.
```bash
coho deploy
```
We'll repeat the deploy command after adding the client side Alpine.js html files (see next sections).
### Create the database and deploy the backend server
To create a database of football players we use this [Kaggle dataset](https://www.kaggle.com/datasets/vivovinco/20212022-football-player-stats). The dataset is a CSV file `2021-2022-Football-Player-Stats.csv` with 2921 players.
We can import the full dataset with the CLI command `coho import`, note that this file has a different separator (_semicolon_) and encoding (_latin1_).
```bash
coho import -f ~/Downloads/2021-2022-Football-Player-Stats.csv -c football --separator ';' --encoding 'latin1'
```
You can also import data in the Studio app, either way you should see a **football** collection like the one shown in the screen shot below.

The database backend is now ready to serve REST API calls from web client applications. Keep reading to learn how to create the Alpine.js web app.
### Create the Alpine.js source files
In the project directory, create a new a sub directory (e.g. `app`) to hold the Alpine.js files.
```bash
mkdir app
```
In the subdirectory _app_, create two new files for the alpine-js source code.
```bash
cd app
touch index.js script.js
```
The project setup is now ready, and your project directory should have the following structure:
```
.
βββ app
β βββ index.html
β βββ script.js
βββ config.json
βββ index.js
βββ node_modules
```
## The Alpine.js frontend application
The Alpine.js app (located in the `app/index.html` file) demonstrates the interactive search page for football players.
The working application is shown in the screen shot below (running locally from the file system).

You can test a [live version of the Alpine.js app here.](https://amiable-zephyr-e964.codehooks.io/static/index.html)
This example app demonstrates two important features in Alpine.js:
1. Loading external data into a global datamodel using [Alpine store](https://alpinejs.dev/globals/alpine-store)
2. Changing the application state by calling a method on the global datamodel when an input area changes using [Alpine events](https://alpinejs.dev/directives/on#keyboard-events)
The `app/index.html` code is shown below, notice line 14,16 with the event listener calling a method on the global datamodel, and line 27-34 iterating on the rows in the global datamodel. Make sure to copy the source code below info your local files to test.
:::note CSS
This example also uses [DaisyUI](https://daisyui.com/) and [Tailwind CSS](https://tailwindcss.com/).
:::
```html {14,16,27-34} title="index.html" showLineNumbers
Player
Squad
Nation
Index
```
The application global state (let's call it `coho`) is an [Alpine store](https://alpinejs.dev/globals/alpine-store) and it's implemented in the `app/script.js` file shown below.
```js {7} showLineNumbers
// replace this with your project url, use CLI command 'coho info' to find yours
const MY_CODEHOOKS_URL = '/football';
// replace this with your own read-only key, use CLI command 'coho add-token' to create a new one
const MY_API_KEY = '0b49638f-56c3-48e9-8725-7f3c20f25316';
document.addEventListener('alpine:init', () => {
Alpine.store('coho', {
loading: false,
search: '',
players: [],
async getData() {
this.loading = true;
this.players = await getDataFromAPI(this.search);
this.loading = false;
},
});
});
// Fetch data from Codehooks REST API
async function getDataFromAPI(search) {
var myHeaders = new Headers();
myHeaders.append('x-apikey', MY_API_KEY); // read-only token
myHeaders.append('Content-Type', 'application/json');
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow',
};
var query = '{}';
console.log('getData', search);
if (search.length > 0) {
query = JSON.stringify({ Player: { $regex: search, $options: 'gi' } });
}
var hints = JSON.stringify({
sort: { Squad: 1, Nation: 1 },
$fields: { Player: 1, Nation: 1, Squad: 1 },
});
var URL = `${MY_CODEHOOKS_URL}?q=${query}&h=${hints}`;
console.log(URL);
const response = await fetch(URL, requestOptions);
return response.json();
}
```
## Deploy the complete Alpine.js front-end and backend application
After adding the client side files as described above, you can run the CLI command `deploy` again.
This will upload the static assets so it can be served under your domain.
```bash
coho deploy
```
Open the `app/index.html` file in your web browser to test the Alpine.js web app locally, or open the auto generated domain, e.g. [https://amiable-zephyr-e964.codehooks.io/static/index.html](https://amiable-zephyr-e964.codehooks.io/static/index.html).
## Conclusion
This tutorial provided you with a hands-on example which combined Alpine.js with a REST API backend using Codehooks.io. By following the steps outlined, you'll gain a deeper understanding of how you can apply these technologies in your own projects.
Source code at [Github here.](https://github.com/RestDB/codehooks-io-examples/tree/main/alpinejs-players)
---
## Streamline Your Backend with JSON Schema on Codehooks.io
*(Note: This content contains MDX/JSX code)*
In modern app development, having a reliable backend is essential. [Codehooks.io](https://codehooks.io) offers a powerful [NoSQL datastore](/docs/nosql-database-api), [JSON schema](https://json-schema.org/) validation, and a secure [REST API](/docs/database-rest-api) to keep your data consistent and your app scalable.

### Why Backend Structure Matters
A well-structured backend is key to:
1. **Data Integrity**: JSON Schema enforces data structure, preventing invalid data from entering your system.
2. **Security**: Strong authentication mechanisms protect your data.
3. **Scalability**: Ready to handle growing data loads as your app expands.
4. **Flexibility**: Seamless REST API integration connects easily with various clients.
### Getting Started with Codehooks.io
1. **Setup**: Start by creating an account on Codehooks.io, installing the CLI, and initializing your project.
```bash
npm install -g codehooks
codehooks login
mkdir myproject && cd myproject
codehooks init
```
Get your project's endpoint URLs:
```bash
codehooks info
```
2. **Define JSON Schema**: Maintain data consistency by defining a JSON Schema.
Example `personSchema.json`:
```js
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"firstName": { "type": "string" },
"lastName": { "type": "string" },
"age": { "type": "integer", "minimum": 0 },
"email": { "type": "string", "format": "email" }
},
"required": ["firstName", "lastName", "email"]
}
```
Apply the schema to your collection:
```bash
codehooks add-schema --collection 'person' --schema './personSchema.json'
```
3. **Import Data**: Easily import data in JSON, CSV, or Excel format.
```bash
codehooks import --filepath './persondata.json' --collection 'person'
```
4. **Use the REST API**: Interact with your data via the REST API.
Example for creating a record:
```javascript
const newRecord = {
lastName: 'Doe',
email: 'john.doe@example.com',
age: 30,
};
fetch('https://your-project-url/person', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer YOUR_JWT_TOKEN',
},
body: JSON.stringify(newRecord),
})
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error('Error:', error));
```
JSON Schema validation ensures only data that fits your schema gets through, returning a `400` error if it doesn't.
For example, if a required field is missing, the validator will return a `400` error like this:
```js
{
"schemaError": [
{
"instancePath": "",
"schemaPath": "#/required",
"keyword": "required",
"params": {
"missingProperty": "firstName"
},
"message": "must have required property 'firstName'"
}
]
}
```
### Codehooks.io Studio
Prefer using a UI? [Codehooks Studio](https://codehooks.io/docs/#codehooks-studio) offers a clean interface for managing your datastore, importing data, and setting up schemasβall without needing to write any code.

### Wrapping Up
Using JSON Schema on Codehooks.io ensures your backend is solid, with enforced data validation, ready-made APIs, and the scalability to grow with your app. Let Codehooks.io manage the backend so you can focus on building your application.
### Further Reading and Learning
- **[Understanding JSON Schema](https://json-schema.org/understanding-json-schema/)**
- A comprehensive guide to defining and using JSON Schema for data validation.
- **[RESTful API Design Best Practices](https://restfulapi.net/rest-api-design-tutorial-with-example/)**
- Best practices for designing and implementing RESTful APIs.
- **[JavaScript Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)**
- Learn how to use the Fetch API in JavaScript for making HTTP requests.
- **[Securing APIs with JWT](https://auth0.com/docs/tokens/json-web-tokens)**
- A guide to securing your APIs using JSON Web Tokens (JWT).
---
## SQL vs NoSQL: When to use and key differences
*(Note: This content contains MDX/JSX code)*
# SQL vs NoSQL: When to use?
Choosing between SQL and NoSQL databases is a crucial decision that can significantly impact your project's success. This guide provides an in-depth comparison of SQL vs NoSQL, helping you understand their differences and choose the right database type for your needs.

## SQL vs NoSQL Overview
1. [SQL vs NoSQL: Key Differences](#sql-vs-nosql-key-differences)
2. [SQL vs NoSQL Performance Comparison](#sql-vs-nosql-performance-comparison)
3. [Practical Examples: SQL vs NoSQL in Action](#practical-examples-sql-vs-nosql-in-action)
4. [SQL to NoSQL Query Mapping Examples](#sql-to-nosql-query-mapping-examples)
5. [Popular Backend Services and Their Database Types](#popular-backend-services-and-their-database-types)
6. [Conclusion: Making the Right Choice](#conclusion-making-the-right-choice)
## SQL vs NoSQL: Key Differences
Before diving into the details of each database type, let's compare the key differences between SQL and NoSQL:
| Feature | SQL Databases | NoSQL Databases |
| --------------- | ----------------------------- | ------------------------------------------- |
| Data Model | Structured, table-based | Flexible (document, key-value, graph, etc.) |
| Schema | Predefined, fixed | Dynamic, flexible |
| Scalability | Vertical | Horizontal |
| ACID Compliance | Yes | Varies (some offer ACID compliance) |
| Consistency | Strong | Eventual (in most cases) |
| Use Cases | Complex queries, transactions | High traffic, big data, real-time web apps |
:::info What is ACID?
ACID is an acronym that stands for Atomicity, Consistency, Isolation, and Durability. These properties ensure database transactions are processed reliably:
- **Atomicity**: All operations in a transaction succeed or the entire transaction is rolled back.
- **Consistency**: A transaction brings the database from one valid state to another.
- **Isolation**: Concurrent transactions do not interfere with each other.
- **Durability**: Once a transaction is committed, it remains so, even in the event of power loss or system failures.
:::
SQL databases are known for strong ACID compliance, while NoSQL databases may sacrifice some ACID properties for increased performance and scalability.
## SQL vs NoSQL Performance Comparison
| Aspect | SQL | NoSQL |
| ----------------- | ------------------------------------- | ------------------------------ |
| Read Performance | Excellent for complex queries | Excellent for simple queries |
| Write Performance | Good, but can slow with scale | Excellent, especially at scale |
| Scalability | Vertical (harder to scale) | Horizontal (easier to scale) |
| Large Datasets | Can struggle with very large datasets | Handles large datasets well |
| Complex Joins | Excellent | Limited or not supported |
The performance difference between SQL and NoSQL can vary greatly depending on the specific use case and implementation.
## Practical Examples: SQL vs NoSQL in Action
Let's compare how you might implement a simple data storage and retrieval operation in both SQL and NoSQL. These examples highlight the syntactical and conceptual differences between the two approaches.
### SQL Example (using PostgreSQL):
```sql
-- Create a table
CREATE TABLE items (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
category VARCHAR(50)
);
-- Insert data
INSERT INTO items (name, category) VALUES ('Hammer', 'tools');
-- Retrieve data
SELECT * FROM items WHERE category = 'tools';
```
### NoSQL Example (using Codehooks.io API):
```javascript
import { app, datastore } from 'codehooks-js';
// Store data
app.post('/items', async (req, res) => {
const conn = await datastore.open();
const result = await conn.insertOne('items', req.body);
res.json(result);
});
// Retrieve data
app.get('/items', async (req, res) => {
const conn = await datastore.open();
conn.getMany('items', { category: 'tools' }).json(res);
});
```
As demonstrated, SQL requires a predefined schema and uses structured query language, while NoSQL offers a more flexible, schema-less approach with a simpler API. Each has its advantages depending on your specific use case.
## SQL vs NoSQL: Query Examples for comparison
The following list shows [SQL](https://en.wikipedia.org/wiki/SQL) example statements expressed as NoSQL queries. Note that this is specific to codehooks.io (which use a query language similar to MongoDB). Read more about codehooks.io NoSQL query language in the [codehooks.io documentation](/docs/nosql-database-query-language).
#### `SELECT * FROM users`
```js
/*
* SQL statement:
* SELECT * FROM users
* expressed as a nosql database query
*/
const db = await Datastore.open();
db.find('users');
```
#### `SELECT user_id, status FROM users`
```js
const query = {};
const opt = {
hints: { $fields: { user_id: 1, status: 1 } },
};
db.find('users', query, opt);
```
#### `SELECT * FROM users Β WHERE status = "A"`
```js
const query = { status: 'A' };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE status != "A"`
```js
const query = { status: { $not: 'A' } };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE status = "A" AND age = 50`
```js
const query = { status: 'A', age: 50 };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE status = "A" OR age = 50`
```js
const query = { $or: [{ status: 'A' }, { age: 50 }] };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE age > 25`
```js
const query = { age: { $gt: 25 } };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE user_id like "bc%"`
```js
const query = { user_id: /^bc/ };
db.find('users', query);
```
#### `SELECT * FROM users Β WHERE status = "A" ORDER BY name ASC`
```js {5}
// Use the CLI to create a sorted index
// $ codehooks createindex --collection users --index name
const query = { status: 'A' };
const opt = {
sort: { name: 1 },
};
db.find('users', query, opt);
```
#### `SELECT * FROM users Β WHERE status = "A" ORDER BY name DESC`
```js {6}
// Use the CLI to create a sorted index
// $ codehooks createindex --collection users --index name
const query = { status: 'A' };
const opt = {
sort: { name: -1 },
};
db.find('users', query, opt);
```
#### `SELECT COUNT(*) FROM users`
```js
const query = {};
const opt = {
hints: { $onlycount: true },
};
db.find('users', query, opt);
```
#### `SELECT COUNT(*) FROM usersΒ WHERE age > 30`
```js
const query = { age: { $gt: 30 } };
const opt = {
hints: { $onlycount: true },
};
db.find('users', query, opt);
```
#### `SELECT * FROM users LIMIT 1`
```js
const query = {};
const opt = { limit: 1 };
db.find('users', query, opt);
```
#### `SELECT * FROM users LIMIT 5 SKIP 10`
```js
const query = {};
const opt = {
limit: 5,
offset: 10,
};
db.find('users', query, opt);
```
## SQL vs NoSQL: Best Use Cases
| SQL Use Cases | NoSQL Use Cases |
| ------------------------ | ------------------- |
| Financial systems | Real-time big data |
| ERP systems | Content management |
| CRM applications | IoT applications |
| E-commerce platforms | Social networks |
| Legacy systems migration | Gaming applications |
Choose SQL when you need ACID compliance, complex queries, and have a well-defined, stable schema. Opt for NoSQL when dealing with large volumes of unstructured data, requiring high scalability, or needing rapid development with changing data models.
## Popular Backend Services and Their Database Types
When choosing a backend service for your project, it's important to understand which database type they use. Here's a list of popular backend services and their primary database types:
| Service | Primary Database Type | Notes |
| ------------------------------------------------------------------------ | --------------------- | ----------------------------------------------------------- |
| [Supabase](https://supabase.com/) | SQL (PostgreSQL) | Offers real-time capabilities and PostgREST API |
| [Firebase](https://firebase.google.com/) | NoSQL | Uses Cloud Firestore, a document-based NoSQL database |
| [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) | NoSQL | Managed MongoDB service, document-based NoSQL |
| [AWS RDS](https://aws.amazon.com/rds/) | SQL | Supports multiple SQL engines (MySQL, PostgreSQL, etc.) |
| [Restdb.io](https://restdb.io/) | NoSQL | Document-based NoSQL with a REST API |
| [Codehooks.io](https://codehooks.io/) | NoSQL | Flexible NoSQL with built-in API and serverless functions |
| [Fauna](https://fauna.com/) | Multi-model | Supports both relational and document models |
| [Azure Cosmos DB](https://azure.microsoft.com/en-us/services/cosmos-db/) | Multi-model | Supports multiple NoSQL models (document, key-value, graph) |
| [Google Cloud Datastore](https://cloud.google.com/datastore) | NoSQL | Schemaless NoSQL datastore |
| [Amazon DynamoDB](https://aws.amazon.com/dynamodb/) | NoSQL | Key-value and document database |
This list showcases the diversity of database options available in modern backend services. Some services, like Fauna and Azure Cosmos DB, even offer multi-model capabilities, allowing you to leverage both SQL and NoSQL paradigms within a single service.
When selecting a backend service, consider not only the database type but also factors such as:
- Scalability and performance
- Pricing model (fixed or usage based)
- Integration with other services
- Developer experience and tooling
- Compliance and security features
Remember that the best choice depends on your specific project requirements, expected data volume, and development team's expertise.
## Conclusion: Making the Right Choice
Choosing between SQL and NoSQL depends on your specific project requirements. Consider factors such as data structure, scalability needs, consistency requirements, and development flexibility when making your decision.
SQL might be the better choice if you need:
- Strong data consistency
- Complex queries and transactions
- A predefined, stable schema
NoSQL could be more suitable if you require:
- Flexibility in data structure
- High scalability for large amounts of data
- Faster performance for simple read/write operations
Remember, it's not always an either-or decision. Some projects benefit from using both SQL and NoSQL databases to leverage the strengths of each.
At Codehooks.io, we offer a powerful NoSQL Database API that combines the flexibility of NoSQL with the ease of use of a REST API. It's an excellent choice for projects that require rapid development and scalability.
---
Ready to explore NoSQL for your project? Check out the [Codehooks.io NoSQL Database API](/docs/nosql-database-api) to get started.
---
## API Integration: Meaning, Tools, and Step-by-Step Guide with Examples
# API Integration: Meaning, Tools, and Step-by-Step Guide with Examples
APIs (Application Programming Interfaces) are crucial for connecting different systems, enhancing software capabilities, and creating seamless user experiences. This guide will provide examples to walk you through understanding the meaning of API integration, implementing, and overcoming challenges in API integration, making the process easier and more efficient.

### Understanding API Integration
#### What is API Integration?
API integration is the process of connecting two or more software applications through their APIs to enable data exchange and extend the functionality of your applications. APIs allow disparate systems to communicate with each other, bridging gaps and providing a streamlined flow of information.
Imagine you are building an application and want to include weather updates, payment services, or social media sharing options. Instead of building these features from scratch, API integration allows you to access existing functionalities from other services, which saves both time and effort.
### Why API Integration Matters
#### Enhancing Application Functionality
API integration enhances your application's capabilities by allowing you to leverage powerful external services. For instance, integrating a payment gateway API adds payment processing capabilities to your app without building a payment solution yourself. Similarly, APIs for analytics, location services, or messaging systems can enhance your application by providing additional features that users expect today.
Additionally, APIs reduce development costs and allow you to build more robust applications faster by reusing existing solutions.
### How to do API integration
#### Practical Steps for Seamless API Integration
1. **Understand the API Documentation**: Start by thoroughly reviewing the API documentation. Documentation provides a roadmap for how to authenticate, make requests, handle responses, and understand error messages. It often includes examples of how to use the API, which can be valuable.
2. **Get API Access**: Obtain the necessary API keys or tokens. Most services require authentication to ensure only authorized clients can access their APIs.
3. **Set Up Authentication**: Choose the appropriate authentication method (e.g., API key, OAuth). Implement it in your code to establish a secure connection to the service.
4. **Choose the Right Tools**: Use the appropriate libraries or tools for your environment. Most programming languages offer packages or modules that simplify working with HTTP requests and parsing responses.
5. **Test the API Integration**: Use tools like [Postman](https://www.postman.com/), [Bruno](https://www.usebruno.com/) or cURL to manually test API endpoints. Make sure the API responses meet your expectations before you add them into your application's codebase.
6. **Handle Errors Gracefully**: Expect that things may go wrong. Build error handling into your API integration to handle common issues like network failures or rate limits.
7. **Monitor and Maintain**: APIs evolve over time, so it's important to keep your integration updated. Monitor API deprecations or updates to ensure compatibility with future versions.
### Explore Integration Tools
#### Essential Tools for API Integration Success
The right tools can significantly streamline your API integration process. Here are key categories of tools that can enhance your development workflow:
**Testing and Development Tools**
- **[Postman](https://www.postman.com/), [Bruno](https://www.usebruno.com/), [Insomnia](https://insomnia.rest/)**: Interactive API testing platforms that allow you to test endpoints, manage collections, and collaborate with team members
- **[cURL](https://curl.se/)**: Command-line tool for making HTTP requests, perfect for quick testing and automation scripts
- **[Swagger/OpenAPI](https://swagger.io/)**: Tools for API documentation and testing that help you understand and interact with APIs
**API Management Platforms**
- **[Kong](https://konghq.com/), [AWS API Gateway](https://aws.amazon.com/api-gateway/), [Azure API Management](https://azure.microsoft.com/en-us/products/api-management/)**: Provide authentication, rate limiting, monitoring, and analytics for your APIs
- **[Apigee](https://cloud.google.com/apigee)**: Enterprise-grade API management with advanced security and analytics features
**Integration Platforms**
- **[Zapier](https://zapier.com/), [Make](https://www.make.com/), [Microsoft Power Automate](https://powerautomate.microsoft.com/)**: No-code platforms for connecting different services and automating workflows
- **[Pipedream](https://pipedream.com/)**: Developer-focused integration platform that combines visual workflows with custom code capabilities
- **[Codehooks.io](https://codehooks.io/)**: Serverless backend platform for building custom APIs that integrate multiple services with built-in databases and job queues, with MCP (Model Context Protocol) support
- **[MuleSoft](https://www.mulesoft.com/), [Dell Boomi](https://boomi.com/)**: Enterprise integration platforms for complex B2B integrations
**Monitoring and Analytics**
- **[Datadog](https://www.datadoghq.com/), [New Relic](https://newrelic.com/)**: Monitor API performance, track errors, and analyze usage patterns
- **[Pingdom](https://www.pingdom.com/), [StatusCake](https://www.statuscake.com/)**: Uptime monitoring to ensure your integrated APIs remain available
**Development Libraries and SDKs**
- **[Axios](https://axios-http.com/) (JavaScript), [Requests](https://requests.readthedocs.io/) (Python), [Guzzle](https://docs.guzzlephp.org/) (PHP)**: HTTP client libraries that simplify API calls in different programming languages
- **Official SDKs**: Many API providers offer language-specific SDKs that handle authentication and provide convenient methods
These tools can help you build more reliable integrations, reduce development time, and maintain better oversight of your API ecosystem.
### Common Challenges in API Integration
#### Overcoming API Integration Hurdles
- **Inconsistent Documentation**: Sometimes API documentation can be outdated or incomplete. In such cases, contacting the provider or joining developer forums can help clarify ambiguities.
- **Authentication and Security**: Setting up authentication (especially OAuth) can be complex. Make sure to follow best practices, such as using secure protocols (HTTPS) and rotating API keys regularly.
- **Handling Rate Limits**: APIs often enforce rate limits to manage the load on their servers. To overcome this, implement retry logic and manage request pacing to avoid hitting these limits.
- **Error Handling**: It's crucial to account for different types of errors like timeouts, 400/500 status codes, and other unexpected responses for the API integration. Use clear logging to understand when and why errors occur. When you have multiple API integrations in one "transaction", make sure you have a strategy to retry or undo changes.
- **Versioning Issues**: API integrations evolve, and newer versions of APIs may not always be backward-compatible. To avoid integration breaking, always test and maintain version compatibility or build flexibility into your code for API changes.
### Building Your Own Backend to Tie APIs Together in one integration
When working with multiple APIs in one integration, it can be challenging to manage different endpoints, authentication methods, and data formats. Building your own backend (also known as Backend for Frontend, or BFF) can help by providing a unified interface for your frontend, simplifying the integration process and making it easier to manage.
:::info Backend for Frontend (BFF)
A Backend for Frontend (BFF) is an architectural pattern where a dedicated backend service is created specifically to support a particular frontend or client application. This approach allows you to tailor your API to the specific needs of your frontend, optimizing data transfer and simplifying client-side logic. BFFs are particularly useful when dealing with multiple external APIs or when you need to aggregate data from various sources before sending it to the client.
:::
Using a backend like the one you can create with [Codehooks.io](https://codehooks.io), you can tie multiple APIs together and provide a single endpoint for your frontend to interact with. This approach offers several benefits:
- **Centralized Logic**: By building a custom backend, you can consolidate business logic that involves multiple APIs. For instance, if your application needs to pull data from both a payment service and a customer management service, you can handle that logic on your backend, simplifying the frontend code.
- **Consistent Data Format**: Different APIs may return data in different formats. Your backend can standardize these formats, ensuring that your frontend only needs to handle a consistent structure. This reduces complexity on the client side and makes your code easier to maintain.
- **Improved Security**: Instead of exposing multiple third-party APIs directly to the frontend, which might involve managing different sets of credentials, your backend can act as a gatekeeper. This way, you only need to secure your own API, keeping the credentials and tokens for the third-party APIs safe.
- **Caching and Rate Limit Management**: By building your own backend, you can implement caching strategies to reduce the number of API calls, improving performance and helping to manage rate limits. Codehooks.io allows you to easily build and deploy backend functions that can handle caching and other data optimizations.
- **Integrated Databases and Worker Queues**: Codehooks.io offers integrated document and key-value databases, which allow you to store and manage data related to API integrations easily. This means you can persist data fetched from various APIs, aggregate information, and provide a more responsive experience for your users. Additionally, worker queues and job scheduling features can help automate tasks such as making periodic API calls, processing large datasets, or handling other background jobs, making the integration process more seamless and efficient.
#### Example: Using Codehooks.io to Build a Unified API
Codehooks.io provides a simple and powerful way to build serverless backends that integrate multiple APIs. For example, suppose you are developing an e-commerce application that needs to integrate with several services:
- A payment gateway for handling transactions
- An inventory management system for tracking stock levels
- A shipping API for calculating delivery costs
Instead of integrating each of these APIs directly in your frontend, you can use Codehooks.io to build a single backend endpoint that coordinates these services. Your backend might expose a `/checkout` endpoint that:
1. **Validates Cart Items**: Calls the inventory API to ensure the items are in stock.
2. **Calculates Shipping Costs**: Fetches delivery options and costs from the shipping API.
3. **Processes Payment**: Interacts with the payment gateway to complete the transaction.
With this setup, your frontend only needs to call the `/checkout` endpoint, while all the complex logic and multiple API calls are handled seamlessly by your backend on Codehooks.io.
:::tip AI-Assisted Development with Codehooks
Codehooks.io provides excellent resources for AI-assisted backend development:
- **[ChatGPT Prompt Template](https://codehooks.io/docs/chatgpt-backend-api-prompt)**: A comprehensive template for generating Codehooks.io backend APIs using ChatGPT and other LLMs
- **[MCP Server](https://github.com/RestDB/codehooks-mcp-server)**: Model Context Protocol server that allows AI agents to directly manage, deploy, and interact with your Codehooks backend through natural conversation
These tools make it incredibly easy to build complex API integrations through simple conversation with AI assistants.
:::
API integration can significantly expand what your applications can do, enabling you to offer richer features while maintaining a smooth user experience. By understanding the process, tackling common challenges, and using a unified backend approach, you can make the most of API integration and create powerful, well-connected software solutions.
---
## How to Use ChatGPT to Build Node.js Backend APIs: Step-by-Step Guide with Codehooks.io
*(Note: This content contains MDX/JSX code)*
# How to Use ChatGPT to Build Node.js Backend APIs: Step-by-Step Guide with Codehooks.io
[ChatGPT](https://chat.openai.com), [GitHub Copilot](https://github.com/features/copilot), [Cursor](https://cursor.sh) and other AI tools using Large Language Models (LLMs) can quickly generate a lot of code, but quite often the process is not straightforward and requires many iterations and a skilled developer to get the best results.
In this article we will show how a **well-structured prompt** can improve the AI-supported development effort.
What makes this guide special is that with Codehooks.io, you can **deploy ChatGPT-generated code directly to production** with minimal or no modifications and minimal setup. The combination of ChatGPT and Codehooks.io's "batteries included" approach means you can go from prompt to production-ready API and database in minutes.
This guide provides a **[practical prompt template](/blog/how-to-use-chatgpt-build-nodejs-backend-api-codehooks#the-chatgpt--llm-prompt-template)** that generates deployment-ready Codehooks.io code. Just copy the template, add your requirements, and let ChatGPT do the heavy lifting.

## Best Practices for Prompting ChatGPT
To get the best results when generating code with ChatGPT for Codehooks.io, I tend to follow these principles:
1. **Provide Examples** β Show the AI correct syntax so it doesn't invent one (included in the template).
2. **Be Explicit About Features** β List key components (e.g., API routes, database interactions, worker queues).
3. **Include a Step-by-Step Structure** β Help the model understand how the code should be structured.
4. **Instructions at the End** β The most important details should come last in the prompt.
## The ChatGPT / LLM Prompt Template
This template follows the principles above and is designed and tested to generate deployment-ready Codehooks.io code.
````
You are an expert in backend development using Codehooks.io. Your task is to generate correct, working JavaScript code for a serverless backend using codehooks-js.
Follow these rules:
- Use the `codehooks-js` package correctly.
- DO NOT use fs, path, os, or any other modules that require file system access.
- Create REST API endpoints using `app.get()`, `app.post()`, `app.put()`, and `app.delete()`.
- Use the built-in NoSQL document database via:
- `conn.insertOne(collection, document)`
- `conn.getOne(collection, ID | Query)`
- `conn.findOne(collection, ID | Query)`
- `conn.getMany(collection, query, options)` // **IMPORTANT:** This function returns a stream - add `.toArray()` to the end of the chain if you need to get out an array to manipulate data (sort, filter, map etc.)
- `conn.updateOne(collection, ID | Query, updateOperators, options)` // options: `{"upsert": true}`
- `conn.updateMany(collection, query, document, options)`
- `conn.replaceOne(collection, ID | Query, document, options)`
- `conn.replaceMany(collection, query, document, options)`
- `conn.removeOne(collection, ID | Query)`
- `conn.removeMany(collection, query, options)`
- **getMany() Options:**
- `sort`: MongoDB sort object (e.g., `{"name": 1}` ascending, `{"createdAt": -1}` descending)
- `limit`: Maximum number of items to return
- `hints`: Field projection - `{$fields: {title: 1, description: 1}}` to include specific fields, `{$fields: {content: 0, _id: 0}}` to omit fields
- `offset`: Number of items to skip for pagination
- Utilize the key-value store with:
- `conn.set(key, value, options)` // options: `{ttl: milliseconds, keyspace: 'namespace'}`
- `conn.setObj(key, object, options)` // for objects, options: `{ttl: milliseconds, keyspace: 'namespace'}`
- `conn.get(key, options)` // options: `{keyspace: 'namespace'}`
- `conn.getObj(key, options)` // options: `{keyspace: 'namespace'}`
- `conn.getAll(keypattern, options)` // options: `{keyspace: 'namespace'}`
- `conn.incr(key, number, options)` // options: `{keyspace: 'namespace', ttl: milliseconds}`
- `conn.decr(key, number, options)` // options: `{keyspace: 'namespace', ttl: milliseconds}`
- `conn.del(key, options)` // options: `{keyspace: 'namespace'}`
- `conn.delAll(keypattern,options)` // options: `{keyspace: 'namespace'}`
- **Note:** All database connection functions are async and return promises.
- Implement worker queues with `app.worker(queueName, workerFunction)` and enqueue tasks using `conn.enqueue(queueName, payload)`.
- Use job scheduling with `app.job(cronExpression, async () => { ... })`.
- Use `app.crudlify()` for instant database CRUD REST APIs with validation. Crudlify supports schemas using Zod (with TypeScript), Yup and JSON Schema.
- Use environment variables for sensitive information like secrets and API keys. Access them using `process.env.VARIABLE_NAME`.
- Generate responses in JSON format where applicable.
- Avoid unnecessary dependencies or external services.
- Always import all required npm packages explicitly. Do not assume a module is globally available in Node.js.
- If a function requires a third-party library (e.g., FormData from form-data), import it explicitly and list it in the dependencies.
- Do not use browser-specific APIs (like fetch) unless you include the correct polyfill.
- Always provide a package.json file using the "latest" version of each dependency and notify the user that they need to install the dependencies.
- Only implement the functionality I explicitly request. Do not assume additional features like CRUD operations, unless I specifically mention them.
- Implement proper error handling and logging.
### Examples of Codehooks.io functionality:
**Creating a simple API:**
```javascript
import { app } from 'codehooks-js';
app.get('/hello', (req, res) => {
res.json({ message: 'Hello, world!' });
});
// MANDATORY: bind to serverless runtime
export default app.init();
```
**Serving Static Files from Deployed Source:**
```javascript
import { app } from 'codehooks-js';
// Serve static files from deployed source directory
app.static({ route: '/img', directory: '/assets/images' });
app.get('/hello', (req, res) => {
res.json({ message: 'Hello, world!' });
});
export default app.init();
```
**Serving Uploaded Files:**
```javascript
import { app } from 'codehooks-js';
// Serve files uploaded with CLI or file-upload tool of the MCP server
app.storage({ route: '/docs', directory: '/mydocuments' });
app.get('/hello', (req, res) => {
res.json({ message: 'Hello, world!' });
});
export default app.init();
```
**Using the NoSQL Document Database:**
```javascript
import { app, Datastore } from 'codehooks-js';
app.post('/orders', async (req, res) => {
const conn = await Datastore.open();
const savedOrder = await conn.insertOne('orders', req.body);
res.json(savedOrder);
});
export default app.init();
```
**Database Queries - Streams vs Arrays:**
When querying data, `getMany()` returns a stream. Use streams for efficient data transfer, use arrays when you need to manipulate data:
```javascript
import { app, Datastore } from 'codehooks-js';
// STREAMING: Use for direct response output (efficient for large datasets)
app.get('/orders-stream', async (req, res) => {
const conn = await Datastore.open();
const orders = conn.getMany('orders', { status: 'pending' });
res.json(orders); // Stream directly to response
});
// ARRAY: Use when you need to manipulate data (sort, filter, transform)
app.get('/orders-sorted', async (req, res) => {
const conn = await Datastore.open();
const orders = await conn
.getMany('orders', { status: 'processed' })
.toArray();
// Now you can sort, filter, or transform the data
orders.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
res.json(orders);
});
// FOREACH: Use for real-time processing and streaming responses
app.get('/orders-stream-processed', async (req, res) => {
res.set('content-type', 'text/plain');
const conn = await Datastore.open();
let count = 0;
const cursor = conn.getMany('orders', { status: 'pending' });
await cursor.forEach((order) => {
// Stream processed data back to client in real-time
res.write(
`Order ${count++} for ${order.customerName} - Amount: ${order.amount}\n`
);
});
res.end();
});
export default app.init();
```
**Using the Key-Value Store:**
```javascript
import { app, Datastore } from 'codehooks-js';
const ONE_DAY = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
// Basic key-value storage
app.post('/settings/:userId', async (req, res) => {
const conn = await Datastore.open();
await conn.set(`settings-${req.params.userId}`, JSON.stringify(req.body));
res.json({ message: 'Settings saved' });
});
// Session management with TTL and keyspace
app.post('/login', async (req, res) => {
const conn = await Datastore.open();
const sessionId = generateSessionId();
await conn.set(
`session-${sessionId}`,
JSON.stringify({
userId: req.body.userId,
loginTime: new Date(),
}),
{
ttl: ONE_DAY,
keyspace: 'sessions',
}
);
res.json({ sessionId });
});
// Cache with shorter TTL
app.get('/expensive-data', async (req, res) => {
const conn = await Datastore.open();
const cacheKey = 'expensive-computation';
let result = await conn.get(cacheKey);
if (!result) {
result = await performExpensiveComputation();
await conn.set(cacheKey, JSON.stringify(result), {
ttl: 10 * 60 * 1000, // 10 minutes
keyspace: 'cache',
});
}
res.json(result);
});
export default app.init();
```
**Implementing a Worker Queue:**
```javascript
import { app, Datastore } from 'codehooks-js';
app.worker('sendEmail', async (req, res) => {
console.log('Processing email:', req.body.payload);
res.end(); // done
});
app.post('/send-email', async (req, res) => {
const conn = await Datastore.open();
await conn.enqueue('sendEmail', req.body);
res.json({ message: 'Email request received' });
});
export default app.init();
```
**Scheduling Background Jobs:**
```javascript
import { app } from 'codehooks-js';
app.job('0 0 * * *', async () => {
console.log('Running scheduled task...');
res.end(); // done
});
export default app.init();
```
**Instant CRUD API with Validation:**
```javascript
import { app } from 'codehooks-js';
import * as Yup from 'yup';
const customerSchema = Yup.object({
name: Yup.string().required(),
email: Yup.string().email().required(),
});
app.crudlify({ customer: customerSchema });
// bind to serverless runtime
export default app.init();
```
**Complete REST API Example:**
```javascript
import { app, Datastore } from 'codehooks-js';
// Get all items using getMany with toArray() to get an array
app.get('/api/items', async (req, res) => {
const conn = await Datastore.open();
const items = await conn.getMany('items', {}).toArray();
res.json(items);
});
// Create item
app.post('/api/items', async (req, res) => {
const conn = await Datastore.open();
const result = await conn.insertOne('items', {
...req.body,
createdAt: new Date(),
});
res.json(result);
});
// Update item
app.put('/api/items/:id', async (req, res) => {
const conn = await Datastore.open();
const result = await conn.updateOne('items', req.params.id, req.body);
res.json(result);
});
// Delete item
app.delete('/api/items/:id', async (req, res) => {
const conn = await Datastore.open();
await conn.removeOne('items', req.params.id);
res.json({ success: true });
});
export default app.init();
```
**Authentication Example:**
```javascript
import { app, Datastore } from 'codehooks-js';
import crypto from 'crypto';
// Protected API routes (require JWT/API key - handled automatically by Codehooks)
app.get('/api/protected', (req, res) => {
res.json({ message: 'This is protected content' });
});
app.get('/api/admin/users', (req, res) => {
res.json({ users: ['user1', 'user2'] });
});
// Public route (no auth needed)
app.get('/api/public', (req, res) => {
res.json({ message: 'This is public' });
});
// Auth hook for public routes - allow access without authentication
app.auth('/api/public', (req, res, next) => {
next(); // Allow public access
});
// Auth hook - called when NO JWT/API key is present
// Use to allow access or create custom authentication logic
app.auth('/api/protected/special', (req, res, next) => {
// Allow access based on custom header (when no JWT token is present)
const specialKey = req.headers['x-special-access'];
if (specialKey === process.env.SPECIAL_ACCESS_KEY) {
next(); // Allow access without JWT
} else {
res.status(401).json({ error: 'Special access required' });
res.end();
}
});
// Auth hook for IP-based access control
app.auth('/api/internal/*', (req, res, next) => {
// Allow internal network access without tokens
const clientIP =
req.headers['x-forwarded-for'] || req.connection.remoteAddress;
if (clientIP.startsWith('192.168.') || clientIP.startsWith('10.')) {
next(); // Allow internal network access
} else {
res.status(403).json({ error: 'Internal network access only' });
res.end();
}
});
// Auth hook for webhook endpoints
app.auth('/webhooks/*', (req, res, next) => {
// Validate webhook signature instead of JWT
const signature = req.headers['x-webhook-signature'];
const payload = JSON.stringify(req.body);
if (validateWebhookSignature(payload, signature)) {
next(); // Allow webhook
} else {
res.status(401).json({ error: 'Invalid webhook signature' });
res.end();
}
});
function validateWebhookSignature(payload, signature) {
// Custom webhook validation logic using Node.js crypto module
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return signature === expectedSignature;
}
export default app.init();
```
** Workflow Example:**
(More information about workflows can be found at https://codehooks.io/docs/workflow-api)
```javascript
import { app } from 'codehooks-js';
// Create a workflow definition
const workflow = app.createWorkflow('simpleTask', 'Basic workflow example', {
// Step 1: Initialize the workflow
begin: async function (state, goto) {
state = {
message: 'Starting workflow',
// Add a random number to demonstrate branching
value: Math.floor(Math.random() * 10),
};
goto('decide', state);
},
// Step 2: Decide which path to take
decide: async function (state, goto) {
// Branch based on whether the value is even or odd
if (state.value % 2 === 0) {
goto('evenPath', state);
} else {
goto('oddPath', state);
}
},
// Step 3a: Handle even numbers
evenPath: async function (state, goto) {
state = {
message: 'Processing even number',
path: 'even',
processed: true,
};
goto('end', state);
},
// Step 3b: Handle odd numbers
oddPath: async function (state, goto) {
state = {
message: 'Processing odd number',
path: 'odd',
processed: true,
};
goto('end', state);
},
// Step 4: End the workflow
end: function (state, goto) {
state = {
final_message: `Workflow completed! Processed ${state.path} number: ${state.value}`,
};
goto(null, state); // workflow complete
},
});
// emitted event when a workflow completes
workflow.on('completed', (data) => {
console.log('Workflow completed:', data);
});
// REST API to start a new workflow instance
app.post('/start', async (req, res) => {
const result = await workflow.start({ foo: 'bar' });
res.json(result);
});
// export app interface to serverless execution
export default app.init();
```
For additional detailed information about the Codehooks.io platform, you can reference https://codehooks.io/llms.txt
[describe what you need here]
````
## Insert Your Request to ChatGPT
To use this template effectively, **insert your request at the bottom** of the prompt. ChatGPT (and other LLMs) will use the provided examples to generate accurate Codehooks.io code.
For example:
```
I need an API that can receive orders from a client and send back a receipt in PDF using Mailgun's REST API. Required fields for the order are name, email, items (array of objects with name and price), total. You can use Yup for validation. Use a worker queue to generate the PDF and send the email.
```
Using the ChatGPT 4o model, this gave me the following working backend API:
```javascript
import { app, Datastore } from 'codehooks-js';
import * as Yup from 'yup';
import PDFDocument from 'pdfkit';
import axios from 'axios';
import FormData from 'form-data';
const MAILGUN_API_KEY = process.env.MAILGUN_API_KEY;
const MAILGUN_DOMAIN = process.env.MAILGUN_DOMAIN;
const MAILGUN_FROM = process.env.MAILGUN_FROM;
// Order validation schema
const orderSchema = Yup.object({
name: Yup.string().required(),
email: Yup.string().email().required(),
items: Yup.array()
.of(
Yup.object({
name: Yup.string().required(),
price: Yup.number().required().positive(),
})
)
.required(),
total: Yup.number().required().positive(),
});
// POST /orders - Create new order
app.post('/orders', async (req, res) => {
try {
// Validate order data
const order = await orderSchema.validate(req.body);
const conn = await Datastore.open();
const savedOrder = await conn.insertOne('orders', order);
// Enqueue worker to generate PDF and send email
await conn.enqueue('processOrder', {
orderId: savedOrder._id,
email: order.email,
});
res.json({
message: 'Order received, receipt will be emailed shortly.',
orderId: savedOrder._id,
});
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// Worker function to generate PDF and send email
app.worker('processOrder', async (req, res) => {
const { orderId, email } = req.body.payload;
const conn = await Datastore.open();
// Retrieve order from database
const order = await conn.getOne('orders', orderId);
if (!order) {
console.error('Order not found:', orderId);
return res.end();
}
// Generate PDF receipt
const pdfBuffer = await generateReceiptPDF(order);
// Send email with the receipt
await sendEmailWithAttachment(email, pdfBuffer, `Receipt_${orderId}.pdf`);
console.log('Receipt sent to:', email);
res.end();
});
// Function to generate PDF receipt
async function generateReceiptPDF(order) {
return new Promise((resolve, reject) => {
const doc = new PDFDocument();
const buffers = [];
doc.on('data', buffers.push.bind(buffers));
doc.on('end', () => resolve(Buffer.concat(buffers)));
doc.on('error', reject);
doc.fontSize(20).text('Order Receipt', { align: 'center' });
doc.moveDown();
doc.fontSize(14).text(`Name: ${order.name}`);
doc.text(`Email: ${order.email}`);
doc.moveDown();
doc.text('Items:');
order.items.forEach((item) => {
doc.text(`- ${item.name}: $${item.price.toFixed(2)}`);
});
doc.moveDown();
doc.text(`Total: $${order.total.toFixed(2)}`, { bold: true });
doc.end();
});
}
// Function to send email via Mailgun
async function sendEmailWithAttachment(to, pdfBuffer, filename) {
const formData = new FormData();
formData.append('from', MAILGUN_FROM);
formData.append('to', to);
formData.append('subject', 'Your Order Receipt');
formData.append('text', 'Please find attached your order receipt.');
formData.append('attachment', pdfBuffer, {
filename,
contentType: 'application/pdf',
});
try {
await axios.post(
`https://api.mailgun.net/v3/${MAILGUN_DOMAIN}/messages`,
formData,
{
auth: { username: 'api', password: MAILGUN_API_KEY },
headers: formData.getHeaders(),
}
);
} catch (err) {
console.error('Failed to send email:', err.response?.data || err.message);
}
}
// Initialize app
export default app.init();
```
## Deploying the API with Codehooks.io
Deploying the new API with Codehooks.io is a straightforward process that allows you to quickly get your backend up and running. You can use either the CLI or the web-based Code Studio.
### Using the web-based Codehooks Studio for development
Using the Codehooks Studio, you simply edit the package.json and the index.js files and replace their contents with the code from the ChatGPT prompt response. Click "NPM install" and "Deploy" and you are done (if it works, that is π). Also remember to set the environment variables in the Settings.
In the studio you can easily see database collections and documents, see live logs, change settings and more.

:::info Continue development on your local machine
If you need to add files to source control or use CoPilot, Cursor or other AI IDEs, you can continue development on your local machine.
Just use the CLI command `codehooks init`:
```bash
codehooks init
? Select project
analytix-yk1e
bsgenerator-94zl
demo-mlyg
β― fideltech-ehyh
junglelab-5cqe
krypto-xogu
mongotest-mitn
(Move up and down to reveal more choices)
```
Select the project and space you used and the files will be downloaded to the current folder.
:::
### Using the Codehooks CLI
Follow these steps to deploy your API using the CLI and ensure it's configured correctly with the necessary environment variables.
#### **Install Codehooks CLI**
If you haven't already, install the Codehooks CLI by running:
```bash
npm install -g codehooks
```
(If you don't have a Codehooks.io account yet, you can create one at [codehooks.io](https://codehooks.io))
#### **Create a new project**
```bash
codehooks create order-api
```
#### **Edit the files**
Edit the index.js and package.json files and paste the code from the ChatGPT prompt response.
#### **Install dependencies**
```bash
npm install
```
#### **Deploy Your Project**
```bash
codehooks deploy
```
#### **Set the environment variables**
```bash
codehooks set-env MAILGUN_API_KEY --encrypted
codehooks set-env MAILGUN_DOMAIN
codehooks set-env MAILGUN_FROM
```
#### **Test (or debug) your new API**
You can test your API by sending requests to the /orders endpoint (you can use Postman or curl for example). To get the URL and curl examples run:
```bash
codehooks info --examples
```
Example curl request:
```bash
curl -X POST "https://lucky-zenith-2f2d.codehooks.io/orders" \
-H "Content-Type: application/json" \
-H "x-apikey: 5773d8fa-992e-4179-b653-d6daa7bf5e11" \
--data-raw '{
"name": "Emma Hansen",
"email": "emma@example.com",
"items": [
{ "name": "Product A", "price": 29.99 },
{ "name": "Product B", "price": 49.99 }
],
"total": 79.98
}'
```
Example response:
```json
{
"message": "Order received, receipt will be emailed shortly.",
"orderId": "66e94d786e3a3d0001a30e14"
}
```
#### **Check the logs (and debug)**
```bash
codehooks logs --follow
```
By following these steps, you can efficiently deploy the new API with Codehooks.io, ensuring that your application is secure and ready for production use. For use by a web client, you would need to add some form of authentication other than API keys.
## Conclusion - A perfect solution ?
Well, no. But it's an improvement.
We've shown how to use a well-structured prompt to help ChatGPT to generate a non-trivial Node.js backend API with Codehooks.io. Being able to go from idea to (almost) production-ready API in minutes is a game changer.
### The main Challenge of Prompting LLMs is consistency
Consistent output is not the norm when prompting LLMs. Given an identical prompt, ChatGPT might produce a different variant of the solution each time. It will probably work, but you would be wise to ask it why it made the choices it did. Providing a structured prompt will get you to a good starting point faster than just asking for code.
### Well structured prompts provide real value
Good prompts provide real value and software developers with good prompting skills can be far more productive than those without. By following the **template approach** with codehooks.io, developers can quickly build and experiment with APIs.
### Next Steps
- Copy the template and try it for your own use case.
- Modify the examples to fit your specific needs.
- Test and refine the LLM-generated code as necessary.
Happy coding! π
---
## Building Stateful Workflows in JavaScript: A Guide to Codehooks Workflow API
*(Note: This content contains MDX/JSX code)*
# π§ Smarter Logic Flows with Codehooks Workflows
At **Codehooks**, our mission is to simplify the development of automations, integrations, and backend APIs. As your app logic grows more complex β onboarding flows, async jobs, conditional steps β it becomes clear: **You need a better way to organize and execute business logic**.
That's why we built the **Codehooks Workflow API** β a lightweight, serverless-friendly way to run **stateful workflows in JavaScript**, without the overhead of bulky orchestrators or external tools.

## β¨ Why Workflows Matter
- **Step-by-step Logic**: Break down complex tasks into simple, ordered steps. Each step works independently, making it easy to find and fix issues.
- **Retries and Conditional Branches**: Automatically retry failed steps and choose different paths based on conditions. Like an if/else statement, but for your whole workflow.
- **Pause/Wait States and Resume Triggers**: Pause workflows until something happens, like getting an API response or user approval. Continues automatically when ready.
- **Persistent State Management**: Safely saves all your workflow data between steps. Keeps track of everything, even if there are errors or delays.
- **Cloud-scale Execution**: Runs smoothly in the cloud with multiple workflows at once. Each workflow runs separately without interfering with others.
- **Code-First & LLM-Ready**: Define workflows in plain JavaScript code, making them perfect for LLM-assisted development. AI can help write, review, and improve your workflows just like any other code.
## Getting Started: Your First Workflow
```js title="index.js"
import { app } from 'codehooks-js'
const workflow = app.createWorkflow('parityCheck', 'Check if a number is even or odd', {
begin: async (state, goto) => {
state.number = Math.floor(Math.random() * 100)
goto('check', state)
},
check: async (state, goto) => {
const step = (state.number % 2 === 0) ? 'even' : 'odd'
goto(step, state) // branch
},
even: async (state, goto) => {
console.log(`${state.number} is even`)
goto('end', state)
},
odd: async (state, goto) => {
console.log(`${state.number} is odd`)
goto('end', state)
},
end: async (state, goto) => {
console.log('Workflow finished', state)
goto(null, state) // null complete the workflow
}
})
// REST API to start a new workflow instance
app.post('/start', async (req, res) => {
const result = await workflow.start({"Some initial": "state"});
res.json(result);
});
// export app interface to serverless runtime
export default app.init();
```
The above JavaScript workflow code can also be visualized as a diagram like the [Mermaid diagram](https://www.mermaidchart.com/) shown below:
```mermaid
flowchart TD
Start([Start]) --> Begin
Begin[begin] -->|generate random number| Check
Check{check} -->|number % 2 === 0| Even
Check -->|number % 2 !== 0| Odd
Even[even] -->|log even number| End
Odd[odd] -->|log odd number| End
End[end] --> Finish([Finish])
style Start fill:#9f9,stroke:#333,stroke-width:2px
style Finish fill:#f99,stroke:#333,stroke-width:2px
style Check fill:#ffd,stroke:#333,stroke-width:2px
style Begin fill:#dff,stroke:#333,stroke-width:2px
style Even fill:#dff,stroke:#333,stroke-width:2px
style Odd fill:#dff,stroke:#333,stroke-width:2px
style End fill:#dff,stroke:#333,stroke-width:2px
```
## Designed for Developers
- **JavaScript-native**: Write workflows in plain JavaScript using async/await. No special syntax to learn - just write normal JavaScript code.
- **Composable**: Break workflows into reusable modules that you can import and combine. Add middleware for common functionality like logging or error handling.
- **Serverless**: Built for serverless from the ground up. Stores workflow state separately from functions to keep memory usage low.
- **Distributed-safe**: Handles concurrent updates safely across multiple workers. Uses atomic operations to prevent data conflicts.
## π€ LLM-Friendly Development
- **Code-First Approach**: Since workflows are defined in plain JavaScript, LLMs can easily understand, generate, and modify workflow code. No need to translate between visual tools and code.
- **Self-Documenting**: Each step is a named function with clear inputs and outputs, making it easy for LLMs to analyze and explain workflow logic.
- **Pattern Recognition**: Common workflow patterns are expressed as standard JavaScript patterns, allowing LLMs to suggest improvements and best practices.
- **Error Prevention**: LLMs can help catch common workflow issues like missing state transitions or infinite loops before runtime.
## Common Use Cases
- **User Onboarding**: Implement multi-step registration flows with validation, OAuth integration, and role-based access control. Handle edge cases like partial completions and timeouts.
- **Approval Flows**: Create approval workflows where multiple people need to review and approve something. Track who approved what and when, and send reminders for pending approvals.
- **Data Sync and Async Jobs**: Process large datasets with checkpointing and resumable execution. Includes built-in support for rate limiting, batching, and error handling.
- **External Event Triggers**: Listen for events from outside sources like webhooks and message queues. Handles duplicate events and ensures reliable processing.
**π Ready to Build Smarter Backends?**
π [View the Workflow API docs β](/docs/workflow-api)
---
## Best Vibe Coding Tools & Why AI Agents Work Better with Simple Backend Infrastructure
*(Note: This content contains MDX/JSX code)*
# Best Vibe Coding Tools & Why AI Agents Work Better with Simple Backend Infrastructure
Ever notice how some days you're a caffeinated code ninja ready to refactor an entire codebase, while other days you can barely muster the energy to fix a typo? Welcome to the wonderfully human world of **vibe coding** β and the tools that get it.
## The Origin Story: How "Vibe Coding Tools" Became a Thing
The term "vibe coding" and the concept of "vibe coding tools" was popularized by Andrej Karpathy in a [February 2024 tweet](https://x.com/karpathy/status/1886192184808149383?lang=en), where the renowned AI researcher and former Tesla AI director coined the phrase to describe development tools that seem to intuitively understand your current energy level and coding mood β tools that adapt to you rather than forcing you to adapt to them.

The concept resonated deeply with the developer community because it captured something we'd all experienced but never quite articulated. Picture this: It's 2 AM, you're debugging a particularly stubborn API integration, your coffee has gone cold, and suddenly your AI assistant suggests exactly the right solution with minimal fuss. That's a vibe coding tool β it just _gets_ what you need right now.
Karpathy's observation sparked widespread discussion about how our development tools should feel more human-centered. The concept gained traction as developers started sharing their own experiences of tools that felt more intuitive during different coding sessions. Some tools excel when you're in deep-focus mode, others shine during collaborative sessions, and some are perfect for those "I just want to ship something quickly" moments.
## Debunking the Misconception: "Vibe Coding == Can't Code"?
Before we dive deeper, let's address the elephant in the room. Some people mistakenly assume that vibe coding tools are a crutch for developers who "can't really code." This couldn't be further from the truth.
Vibe coding isn't about replacing programming skills β it's about amplifying them. The most skilled developers often embrace these tools because they understand that writing code is just one part of building great software. Senior engineers don't prove their worth by manually typing boilerplate or reinventing wheels; they prove it by solving complex problems efficiently and shipping quality products.
Think of it this way: a master chef doesn't grind their own spices to prove they can cook β they use the best tools available to focus on creating amazing dishes. Similarly, experienced developers use vibe coding tools to eliminate repetitive tasks and focus on architecture, problem-solving, and innovation.
The reality is that vibe coding tools often require _more_ programming knowledge to use effectively, not less. Understanding when and how to leverage AI assistance, knowing which abstractions to trust, and being able to debug generated code all require deep technical expertise.
Vibe coding tools share several characteristics:
- **Contextual Intelligence**: They understand not just what you're coding, but how you're coding
- **Minimal Friction**: They get out of your way when you're in the zone
- **Adaptive UI/UX**: The interface feels right for your current task and energy level
- **Smart Defaults**: They make good assumptions so you can focus on the creative work
- **Mood Flexibility**: They work whether you're feeling methodical or experimental
- **Intelligent Laziness**: They embrace the programmer virtue that being lazy often leads to more elegant, efficient solutions
The last point deserves special mention. As any experienced developer knows, "lazy" programmers often write the best code β not because they cut corners, but because they naturally gravitate toward solutions that require less maintenance, fewer lines of code, and more automation. Why write 50 lines of boilerplate when `app.crudlify()` does it in one? Why manually configure CORS, rate limiting, and database connections when your tool handles it intelligently? Vibe coding tools embody this philosophy by doing the heavy lifting so you can focus on solving actual business problems rather than fighting infrastructure.
## The Essential Vibe Coding Tools Toolkit
The vibe coding tools landscape is exploding with innovation. From AI-powered IDEs to natural language app builders, there are dozens of tools emerging that understand and adapt to developer energy and workflow. The seven tools we'll explore here represent just the tip of the iceberg β a curated selection of standout platforms that exemplify what makes a tool truly "vibe-friendly."
**Real-world vibe scenario**: Sarah's got a weekend project idea β a mood-tracking app for her team. She's feeling creative but not in the mood for infrastructure setup. She spins up a Codehooks backend (`app.crudlify()` for instant CRUD), uses v0 to generate a clean React dashboard, and Cursor to refactor the logic. Three hours later, she's got a working app deployed. That's vibe coding in action β tools that match her energy level and let her focus on the creative problem-solving.
### 1. v0 by Vercel
[v0](https://v0.dev) is a generative AI tool that streamlines Web App development by transforming user input into ready-to-use code. It supports various frameworks such as React, Vue, Svelte, and HTML with CSS, but really works best using React, Tailwind and shadcn/ui components. What makes v0 a perfect vibe tool: You can preview, modify and seamlessly deploy these pages to the cloud.
**The "Rapid Prototyping" Vibe**: v0 excels when you're in that "I need a frontend for my API" mood. You can literally describe your UI in plain English and get React components that work immediately. Perfect for Codehooks developers who want to quickly build frontends for their serverless APIs.
```
// Prompt: "Create a dashboard showing API statistics with charts"
// v0 generates: Complete React component with charts, responsive design, and clean styling
```
**Best Vibe**: When you're feeling experimental and want to see ideas come to life instantly without getting bogged down in setup
### 2. Cursor
[Cursor](https://cursor.com) has quickly become the gold standard for AI-powered code editors. Unlike traditional IDEs with AI bolted on, Cursor was built from the ground up with AI as a first-class citizen. It supports multiple LLMs (Claude, GPT-4, and others), letting you choose the model that matches your current task and preference. It understands your entire codebase context and can make suggestions that anticipate your next move. Like many modern AI coding tools, Cursor supports MCP (Model Context Protocol), enabling seamless integration with external data sources and services.
**The "I'm in the zone" Vibe**: Cursor's AI can follow your coding patterns and suggest entire functions or refactors that match your style. It's like having a coding partner who's read your entire codebase and knows exactly what you're trying to build.
**Best Vibe**: When you're in flow state and want an AI that keeps up with your thought process without breaking your rhythm
### 3. Bolt.new
[Bolt.new](https://bolt.new) is an AI-driven platform that lets you prompt, edit, and deploy full-stack web apps right in your browser. It's built on WebContainers tech, meaning you get a legit dev environmentβNPM packages, Supabase integration, and Netlify deployment included.
**The "Build Everything Fast" Vibe**: Bolt shines when you want to iterate quickly. Its "diffs" feature means you can make changes and see updates instantly without full rebuilds. Think of it as the tool for "weekend hackathon energy" β when you want to build a complete MVP in hours, not days.
**Best Vibe**: When you're in that "let's ship something complete" mood and want AI that can handle frontend, backend, and deployment with speed
### 4. Lovable.dev
[Lovable.dev](https://lovable.dev) emphasizes ease of use and accessibility for full-stack application development. It uses AI to translate user prompts into functional code with minimal input and focuses on collaborative workflows.
**The "Thoughtful Architecture" Vibe**: Unlike Bolt's "ship fast" approach, Lovable takes time to plan. It suggests project structure, discusses trade-offs, and integrates with GitHub for proper version control. Perfect when you're building something that needs to scale or when working with a team. Lovable supports multiple backends, but has a deep integration with Supabase which enables advanced features like schemas, migrations and authentication.
**Best Vibe**: When you're feeling methodical and want an AI that thinks through architecture, considers maintainability, and helps you avoid technical debt from day one
### 5. Claude Code
[Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) is the agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster through natural language commands. This is the ultimate vibe tool for developers who live in the terminal.
**The "Terminal Native" Vibe**: Claude Code maps and explains entire codebases in a few seconds. It uses agentic search to understand project structure and dependencies without you having to manually select context files. When you're feeling like a command-line wizard and want AI that speaks your language.
**Best Vibe**: When you're in deep-work mode and want powerful AI assistance without leaving your terminal environment
### 6. GitHub Copilot & Claude via Extensions
[GitHub Copilot](https://github.com/features/copilot) and Claude (through various VS Code extensions and integrations) have revolutionized the vibe coding experience. Both VS Code and Claude support MCP (Model Context Protocol), creating a powerful ecosystem where AI assistants can access external data sources, databases, and services. While Copilot excels at autocomplete-style suggestions, Claude shines in conversational debugging and architectural discussions through extensions and API integrations. These tools adapt to your coding energy β suggesting simple completions when you're tired, or diving deep into complex refactors when you're energized.
**Best Vibe**: When you're prototyping and want to focus on logic rather than syntax, or when you need a rubber duck that actually codes back
### 7. Cline
[Cline](https://cline.bot) is the autonomous AI coding agent that's taken the developer community by storm with over 1.8M installs and 47.1k GitHub stars. Unlike traditional autocomplete tools, Cline operates as your collaborative AI partner directly within VS Code (and other IDEs), capable of creating files, editing code, executing terminal commands, and even browsing the web. What sets Cline apart is its thoughtful, human-in-the-loop approach β it breaks down complex tasks into clear steps, explains its reasoning, and asks for approval before making changes. With full MCP support, Cline can connect to external databases, live documentation, and hundreds of specialized tools, making it incredibly versatile for different development workflows.
**The "Autonomous Assistant" Vibe**: Cline shines when you're dealing with complex, multi-step tasks or unfamiliar codebases. It's like having a skilled junior developer who can handle the heavy lifting while you focus on architecture and business logic. Perfect for those "I need to scaffold this entire feature" or "help me debug this mysterious issue" moments.
**Best Vibe**: When you're feeling overwhelmed by a large task and want an AI that can break it down, execute it step-by-step, and learn from your codebase context while maintaining full transparency about what it's doing
---
## Backend Complexity Can Kill the Vibe
Here's something I've noticed after using all these tools: the most beautiful AI assistant in the world becomes useless the moment you hit backend complexity. You can have Cursor suggesting perfect React components and Claude helping with brilliant algorithms, but if deploying your app requires juggling AWS services, configuring databases, and debugging CORS issues, your creative flow dies a brutal death.
Even worse is the version nightmare β trying to make a PostgreSQL client, Redis cache, message queue library, job scheduler, and authentication middleware all play nicely together. Each service has its own connection patterns, error handling, and configuration quirks. Your AI assistant might suggest brilliant code, but it can't debug why your job queue isn't talking to your database, or why your authentication tokens work in development but fail in production.
The dirty secret of most "vibe coding" tools is that they solve the easy part (generating code) but ignore the hard part (making it actually work in production). This is why coherent backend approaches matter so much for AI-powered development. When your AI agents can understand your entire stack through a simple, consistent interface, they become exponentially more useful.
Some platforms get this right. **Supabase** deserves huge credit here β their consistent MCP-supported environment works beautifully with tools like Lovable and Bolt.new. When your database, authentication, real-time subscriptions, and edge functions all share the same client patterns and error handling, AI tools can reason about your entire stack instead of just fragments.
Think about it: an AI assistant that can deploy your code, query your database, and handle authentication through a unified API is fundamentally different from one that can only suggest React components while you manually handle everything else. The Model Context Protocol (MCP) is making this vision real, but it requires backend platforms that are designed for AI collaboration from the ground up.
This is why coherent backend platforms matter for vibe coding β not because they're magical, but because they eliminate the integration complexity that breaks your flow. Whether it's Supabase's unified PostgreSQL ecosystem, Codehooks' serverless simplicity (`app.crudlify()` handles database, APIs, and jobs in one abstraction), or other platforms designed for consistency, the key is having fewer moving parts that work together seamlessly.
---
## What Makes Infrastructure Vibe-Friendly (A Slightly Biased Take)
Full disclosure: you're reading this on the Codehooks blog, so take this with a grain of salt. But after using these AI tools extensively, I've learned some things about what infrastructure actually enables vibe coding rather than killing it.
The tools above adapt to your coding style and energy, but they need a solid foundation to shine. Here's what I look for in vibe-friendly backend platforms:
**Codehooks with MCP Integration**: The serverless backend that just works. When your AI tools are suggesting brilliant solutions, the last thing you want is to get bogged down in deployment configs, database setup, or CORS issues. [Codehooks](https://codehooks.io) handles all of this invisibly:
- **Zero-config deployment**: Your Cursor session can deploy directly with no pipeline setup
- **Instant CRUD APIs**: `app.crudlify()` gives you a full backend in one line
- **MCP compatibility**: Your AI assistants can directly interact with your Codehooks projects
- **Workflow API**: AI agents can build complex stateful workflows with automatic retry, state persistence, and queue management
- **Invisible infrastructure**: Authentication, rate limiting, CORS, and database connections handled automatically
```javascript
import { app } from 'codehooks-js';
// The backend that never breaks your vibe
app.crudlify();
// AI agents can even create complex workflows
const workflow = app.createWorkflow('orderProcess', 'Handle customer orders', {
validate: async (state, goto) => {
// AI can suggest complex business logic here
state.validated = true;
goto('process', state);
},
process: async (state, goto) => {
// Stateful processing with automatic retries
goto('complete', state);
},
});
export default app.init();
```
**A note on lock-in**: Yes, using Codehooks represents a (tiny) vendor lock-in, but not more than you could replace in an afternoon with Node.js, Redis, and MongoDB. The `codehooks-js` library is essentially a convenient wrapper around standard web APIs and database operations. Your business logic remains portable, and the abstractions map directly to common patterns any Node.js developer would recognize.
Think of Codehooks as the reliable friend who handles logistics while you focus on the creative work. When v0 generates your frontend and Cursor refines your logic, Codehooks ensures your backend never becomes a bottleneck.
---
## How to Evaluate Vibe Coding Tools
The best vibe coding tools are deeply personal. What feels intuitive to one developer might feel clunky to another. Here's how to evaluate whether a tool will actually improve your workflow:
### The 15-Minute Test
Can you get meaningfully productive within 15 minutes of starting? Great vibe tools have exceptional onboarding. If you're fighting with configuration or unclear documentation in the first quarter-hour, it's probably not going to get better.
### Integration Reality Check
How well does it play with your existing stack? The most powerful individual tool becomes useless if it doesn't integrate with your current workflow. Look for:
- **API compatibility**: Can it talk to your existing services?
- **Data export**: Can you get your work out if needed?
- **Tool chain fit**: Does it complement your current IDE, deployment, and collaboration tools?
### Flow State Test
Does the tool maintain or break your coding flow? Pay attention to:
- **Context switching**: How often does it force you to leave your main coding environment?
- **Interruption patterns**: Does it suggest at the right moments or constantly distract?
- **Learning curve**: Does it get better as you use it more, or stay equally difficult?
### The "Saturday Project" Standard
The ultimate test: Could you use this tool to build and deploy a weekend project without getting frustrated? If a tool can handle the full cycle from idea to production smoothly, it's probably vibe-friendly.
## The Future of Vibe Coding
As AI becomes more prevalent in development, we're seeing tools that adapt to individual coding styles and preferences. The tools we've covered are just the beginning. The next wave will likely include:
- **Cross-tool vibe awareness**: Your Cursor session influencing your Codehooks deployment suggestions, or v0 components automatically integrating with your existing API patterns
- **Energy-based interfaces**: Tools that detect when you're in deep-focus mode vs. exploratory mode and adjust their suggestions accordingly
- **Contextual deployment intelligence**: Platforms that understand not just what you're building, but why and how you prefer to ship
- **Collaborative vibe matching**: Team tools that adapt to the collective energy and working style of your entire dev team
The MCP protocol adoption we're seeing in tools like Codehooks and Cursor suggests a future where vibe coding tools talk to each other, creating seamless workflows that feel almost telepathic.
## Building Your Vibe
The ultimate vibe coding setup is one that feels invisible β tools that amplify your capabilities without demanding attention. The key is understanding that different tools serve different roles in your vibe stack.
**Adaptive development tools** like Cursor, Claude, and v0 adapt to your coding style and energy level. They're the ones that "feel" your mood and adjust their suggestions accordingly, whether you're working on frontend, backend, or full-stack code.
**Backend infrastructure** like Codehooks and Supabase is different β it's the invisible foundation that makes vibe coding possible. When your backend "just works" without configuration, deployment headaches, or infrastructure management, your adaptive AI tools can focus on the creative work. These platforms don't adapt to your coding style; instead, they eliminate the friction that would break your vibe in the first place.
Think of it as the difference between responsive collaborators and reliable foundations. You need both: AI tools that understand your energy and infrastructure that never interrupts your flow.
Remember: the goal isn't to use every trending tool, but to curate a toolkit where frontend intelligence meets backend simplicity. Your vibe coding setup should make you excited to open your laptop and start building.
What's your current coding vibe, and what tools are helping you achieve it? The beauty of vibe coding tools is that they're as unique as the developers who use them.
Start small: pick one category to focus on first and build from there. Maybe begin with a vibe-friendly code editor like Cursor, then add backend infrastructure that doesn't break your flow. The goal isn't to use every tool, but to curate a setup that amplifies your natural coding rhythm.
---
_Ready to experience vibe-friendly backend development? [Try Codehooks.io](https://codehooks.io) and discover why developers love having their infrastructure just work. When your backend vibes with your workflow, everything else falls into place._