Set of utility classes for Cloudflare Workers D1 with validations by Joi inspired by reform.
Note: This package is still considered experimental. Breaking changes should be expected.
- Basic CRUD Model.
- UUID by default.
- Timestamps for created_at and updated_at.
- Validations by Joi.
- Raw SQL queries.
- Install
- Example
- Methods
- Extend Methods
npm:
npm install model-one joi
yarn:
yarn add model-one joi
In the following example we are going to define an user with the following fields first_name and last_name.
- Create a new database.
Create a local file schema.sql
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id text PRIMARY KEY,
first_name text,
last_name text,
deleted_at datetime,
created_at datetime,
updated_at datetime
);
Creates a new D1 database and provides the binding and UUID that you will put in your wrangler.toml file.
npx wrangler d1 create example-db
Create the tables from schema.sql
npx wrangler d1 execute example-db --file ./schema.sql
- We need to import the Model and Schema from 'model-one' and the type SchemaConfigI. Then create a new Schema, define table name and fields
// ./models/User.ts
import { Model, Schema } from 'model-one'
import type { SchemaConfigI } from 'model-one';
const userSchema: SchemaConfigI = new Schema({
table_name: 'users',
columns: [
{ name: 'id', type: 'string' },
{ name: 'first_name', type: 'string' },
{ name: 'last_name', type: 'string' }
],
})
- Then we are going to define the interfaces for our User model.
// ./interfaces/index.ts
export interface UserDataI {
id?: string
first_name?: string
last_name?: string
}
export interface UserI extends Model {
data: UserDataI
}
- Now we are going import the types and extend the User
// ./models/User.ts
import { UserI, UserDataI } from '../interfaces'
export class User extends Model implements UserI {
data: UserDataI
constructor(props: UserDataI) {
super(userSchema, props)
this.data = props
}
}
- Final result of the User model
// ./models/User.ts
import { Model, Schema } from 'model-one'
import type { SchemaConfigI } from 'model-one';
import { UserI, UserDataI } from '../interfaces'
const userSchema: SchemaConfigI = new Schema({
table_name: 'users',
columns: [
{ name: 'id', type: 'string' },
{ name: 'first_name', type: 'string' },
{ name: 'last_name', type: 'string' }
],
})
export class User extends Model implements UserI {
data: UserDataI
constructor(props: UserDataI) {
super(userSchema, props)
this.data = props
}
}
- After creating the User we are going to create the form that handles the validations. And with the help of Joi we are going to define the fields.
// ./forms/UserForm.ts
import { Form } from 'model-one'
import { UserI } from '../interfaces'
import Joi from 'joi'
const schema = Joi.object({
id: Joi.string(),
first_name: Joi.string(),
last_name: Joi.string(),
})
export class UserForm extends Form {
constructor(data: UserI) {
super(schema, data)
}
}
To insert data we need to import the UserForm and we are going start a new User and insert it inside the UserForm, then we can call the method create.
// ./controllers/UserController.ts
import { UserForm } from '../form/UserForm';
import { User } from '../models/User';
const userForm = new UserForm(new User({ first_name, last_name }))
await User.create(userForm, binding)
By importing the User model will have the following methods to query to D1:
// ./controllers/UserController.ts
import { User } from '../models/User';
await User.all(binding)
await User.findById(id, binding)
await User.findOne(column, value, binding)
await User.findBy(column, value, binding)
Include the ID and the fields you want to update inside the data object.
// ./controllers/UserController.ts
import { User } from '../models/User';
// User.update(data, binding)
await User.update({ id, first_name: 'John' }, binding)
Delete an User
// ./controllers/UserController.ts
import { User } from '../models/User';
await User.delete(id, binding)
Extend User methods.
// ./models/User.ts
import { Model, Schema, NotFoundError } from 'model-one'
import type { SchemaConfigI } from 'model-one';
import { UserI, UserDataI } from '../interfaces'
const userSchema: SchemaConfigI = new Schema({
table_name: 'users',
columns: [
{ name: 'id', type: 'string' },
{ name: 'first_name', type: 'string' },
{ name: 'last_name', type: 'string' }
],
})
export class User extends Model implements UserI {
data: UserDataI
constructor(props: UserDataI) {
super(userSchema, props)
this.data = props
}
static async findByFirstName(first_name: string, binding: any) {
// this.findBy(column, value, binding)
return await this.findBy('first_name', first_name, binding)
}
static async rawAll(binding: any) {
const { results, success } = await binding.prepare(`SELECT * FROM ${userSchema.table_name};`).all()
return Boolean(success) ? results : NotFoundError
}
}
- Support JSONB.
- Soft and hard delete.
- Tests.
- Unique values.
- Associations: belongs_to, has_one, has_many.
- Complex Forms for multiple Models.
Julian Clatro
MIT