Strapi ORM
✨ StrapiORM for backend requests inspired by TypeORM that abstracts filtering, population and sorting ✨
Motivation
Strapi is a great headless CMS, and a good entry point for start-ups. Sometimes it might become a core part of the project, but it can't substitute a strong backend application. This is where StrapiORM comes in. It allows the backend applications to treat Strapi as a database, and abstracts the filtering, population and sorting of the entities.
Install
npm i @vicodes/strapi-orm
Example
First create a Manager instance
const manager = new Manager({
baseUrl: 'http://localhost:1337/api',
accessToken: 'super-secret-token',
flatten: true,
})
Then create the entities that represent Strapi Content Types.
Use the StrapiEntity
decorator to specify the URI path of the Strapi Content Type, either as a string or as a
StrapiEntityOptions
object with the following properties:
interface StrapiEntityOptions {
path: string
}
import { StrapiEntity } from './strapi-entity.decorator'
@StrapiEntity('scopes')
export class Scope {
id: number
name: string
}
@StrapiEntity({ path: 'roles' })
export class Role {
id: number
name: string
scope: Scope
}
@StrapiEntity('users')
export class User {
id: number
firstName: string
lastName: string
role: Role
}
Using Repository
import { Manager } from '@vicodes/strapi-orm'
import { User } from './user.entity'
import { StrapiRepository } from './strapi.repository'
// It's important to type the repository with the entity type to get maximum of type safety
const roleRepository: StrapiRepository<Role> = manager.getRepository(Role)
const scopeRepository: StrapiRepository<Scope> = manager.getRepository(Scope)
const scope = await scopeRepository.findById(1)
const adminRole: Role = {
name: 'admin',
scope: scope,
}
await roleRepository.create(adminRole)
Using QueryBuilder
You can use the QueryBuilder to create complex queries. This will create the query params to select, populate filter and sort without the hustle of creating the query string or object.
import { Manager } from '@vicodes/strapi-orm'
import { User } from './user.entity'
import { StrapiRepository } from './strapi.repository'
const manager = new Manager({
baseUrl: 'http://localhost:1337/api',
accessToken: 'super-secret-token',
flatten: true,
})
const respository: StrapiRepository<User> = manager.getRepository(User)
const users = await respository
.createQueryBuilder()
.select(['firstName', 'lastName'])
.populate('role')
.populate('role.scope', '*', { name: { $in: ['read-only', 'read-write'] } })
.where('role.scope.name', 'read-only')
.getMany()
Overriding the default request service
StrapiORM uses a default request service that uses fetch
to make the requests to the Strapi API.
It can be overridden by passing a custom request service to the Manager constructor.
import { Manager, RequestService } from '@vicodes/strapi-orm'
class CustomRequestService extends RequestService {
constructor(config: ConnectionConfig) {
super(config)
}
getAuthHeaders(): Record<string, string> {
return {
Authorization: 'Basic user:password',
}
}
handleResponse<Entity>(jsonResponse: unknown): Promise<Entity> {
// Do something with the response, like modifying the data
// By default here is where the response is flattened
}
request<Entity>(path: string, requestOptions: RequestOptions): Promise<Entity> {
// Use your custom request library, like axios
}
}
API
Manager
new Manager(options: ConnectionConfig, requestService?: RequestService)
Creates a new Manager
instance.
ConnectionConfig
is an object with the following properties:
Property | Type | Description | Required | Default |
---|---|---|---|---|
baseUrl | string | Base url of the Strapi API | true | |
accessToken | string | Access token of the Strapi API | true | |
flatten | boolean | Flatten the response from the Strapi API, removing the attributes and data properties | false | false |
manager.getRepository(target: Entity): Repository<Entity>
:
Returns a new Repository instance for the given entity.
Repository
repository.find(): Promise<Entity[]>
- Return all entities
repository.findBy(where: FindOptionsWhere<Entity>): Promise<Entity[]>
- Return all entities that match the given where clause.
The were clause is an key/value object where the key is the field name and the value is the value to match.
For example: `repository.findBy({ name: 'John' })` will return all entities where the name is John.
repository.findOneById(id: number | string): Promise<Entity>
- Finds an entity by its id.
repository.findOneBy(where: FindOptionsWhere<Entity>): Promise<Entity[]>
- Same as `#findBy` but it will return only one element. The first in the list
repository.create(entity: Entity): Promise<Entity>
- Creates a new entity.
repository.update(id: number | string, entity: Entity): Promise<Entity>
- Updates an entity by its id.
repository.delete(id: number | string): Promise<Entity>
- Deletes an entity by its id.
repository.createQueryBuilder(): StrapiQueryBuilder<Entity>
- Creates a new `StrapiQueryBuilder` instance for the given entity.
QueryBuilder
queryBuilder.select(fields: string | string[]): SelectQueryBuilder<Entity>
- Selects the fields to be returned by the query.
queryBuilder.populate(relation: string, fields?: string | string[], filter?: StrapiFilter, sort?: StrapiSort): SelectQueryBuilder<Entity>
- Populates the given relation of the entity. Optionally, you can select the fields to be returned and filter the populated entities.
To populate a nested relation, use dot notation: `queryBuilder.populate('role.scope')`. There is no limit of nested relations depth.
The `populate` method can be called multiple times to populate multiple relations.
Essentially it's a `LEFT JOIN` query, where filters can be passed to filter the populated entities, but it won't filter
out the main entity. For that use the `where` method below.
queryBuilder.where(field: string, filters: StrapiFilter): SelectQueryBuilder<Entity>
- Filters the entities by the given field and value. This follows the same syntax as the Strapi API filters.
Available filters are defined in the [StrapiFilter](src/types/filter.type.ts) type
queryBuilder.getMany(): Promise<Entity[]>
- Executes the query and returns the entities.
queryBuilder.getOne(): Promise<Entity>
- Executes the query and returns the first entity.