Experience the power of SonicJs, a cutting-edge Headless CMS built on the robust Cloudflare Workers platform. SonicJs revolutionizes API performance, delivering an astounding average speed improvement of π₯π₯π₯ 6 times faster π₯π₯π₯ than a standard node application.
Read the docs here [https://sonicjs.com]
Platform | Average Response Time | Difference |
---|---|---|
Strapi | 342.1ms | - baseline - |
Node + Postgres | 320.2ms | 1.06x Faster |
SonicJs | 52.7ms | 6.4x Faster |
The details of our performance benchmark here is available at here.
npm install -g wrangler
wrangler kv:namespace create sonicjs
wrangler kv:namespace create SonicJS --preview
wrangler d1 create SonicJS
If you receive the error:
wrangler.ps1 cannot be loaded because running scripts is disabled on this system.
Run this command on powershell as administrator and try running the wrangler commands from step 3:
Set-ExecutionPolicy RemoteSigned
If you already created a namespace and need to see your namespace id do:
wrangler kv:namespace list
npx create-sonicjs-app
Follow the installation script prompts to enter the required Cloudflare values.
One last step; we need to run the migration scripts to create our database tables:
npm run up
Now you're ready to fire up SonicJs!
npm run dev
Open the admin interface at: http://localhost:8788
Check out https://sonicjs.com/getting-started for next steps.
Configure array fields by first telling drizzle that the field is an array field.
tags: text("tags", { mode: "json" }).$type<string[]>(),
Then configure the field in the exported fields
variable from a table file.
export const fields: ApiConfig["fields"] = {
tags: {
type: "string[]",
},
};
Configure file fields in the exported fields
variable from a table file.
You can configure which bucket to use to upload to as well as the path to store the file in the bucket when uploaded from that field.
Picking an existing file is also an option on the form, it will list any other files in that same bucket and path.
export const definition = {
id: text("id").primaryKey(),
title: text("title"),
body: text("body"),
userId: text("userId"),
image: text("image"),
images: text("images", { mode: "json" }).$type<string[]>(),
tags: text("tags", { mode: "json" }).$type<string[]>(),
};
export const fields: ApiConfig["fields"] = {
image: {
type: "file",
bucket: (ctx) => ctx.env.R2STORAGE,
path: "images",
},
images: {
type: "file[]",
bucket: (ctx) => ctx.env.R2STORAGE,
path: "images",
},
tags: {
type: "string[]",
},
};
A tus api is available for uploading files. The tus api is available at /tus
.
In addition to the normal tus api 3 headers should be passed in the request to properly handle finding the correct bucket, the path to store the file, permissions, hooks, etc.
Important: There are two options for how passwords are stored (key derivation functions), set by the AUTH_KDF env variable. These effect the security of your passwords if they were to ever leak, as well as how much cpu time is used when a user is created, changes their password, or logs in.
If you change your auth options old users will still be able to login but the encryption won't change for their password until they change their password.
https://sonicjs.com/environment-variables
{
"email": "user@sonicjs.com",
"password": "password123"
}
{
"bearer": "eo0t9q52njemo83rm1qktr6kwjh8zu5o3vma1g6j"
}
const url = "http://localhost:8788/v1/posts/c1d462a4-fd10-4bdb-bbf2-2b33a94f82aa";
const data = {
"data": {
"title": "Test Post Update"
}
};
const requestOptions = {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer eo0t9q52njemo83rm1qktr6kwjh8zu5o3vma1g6j'
},
body: JSON.stringify(data)
};
fetch(url, requestOptions)
See the Access Control Readme
The hooks
property on the ApiConfig
type allows configuring functions that run at certain points in the request lifecycle. Here are the available hooks:
The resolveInput
hook allows transforming the input data before running a create or update operation.
resolveInput: {
create: (ctx: AppContext, data: any) => any | Promise<any>;
update: (ctx: AppContext, id: string, data: any) => any | Promise<any>;
}
For example, it can be used to automatically populate the userId
field based on the authenticated user:
resolveInput: {
create: (ctx, data) => {
if (ctx.get("user")?.userId) {
data.userId = ctx.get("user").userId;
}
return data;
}
}
The hooks receive the context (ctx
) containing the request information, as well as the input data
. They can return a Promise of the transformed data.
The beforeOperation
hook runs before executing the database operation:
beforeOperation?: (
ctx: AppContext,
operation: "create" | "read" | "update" | "delete",
id?: string,
data?: any
) => void | Promise<void>;
It receives:
ctx
- the contextoperation
- the operation being performed id
- the document ID (if applies)data
- the input data (if applies)For example, it can be used for logging.
The afterOperation
hook runs after executing the database operation:
afterOperation?: (
ctx: AppContext,
operation: "create" | "read" | "update" | "delete",
id?: string,
data?: any,
result?: { data?: any } & Record<string, any>
) => void | Promise<void>;
It receives:
ctx
- the contextoperation
- the operation performedid
- the document ID (if applies) data
- the input data (if applies)result
- the operation resultFor example, it can be used for logging or post-processing the result.
The legacy version of SonicJs (a Node.js based web content management system) can be found here: https://github.com/lane711/sonicjs/tree/legacy