✨ StrapiORM for backend requests inspired by TypeORM that abstracts synchronization, filtering, population and sorting ✨
Strapi is a great headless CMS, and a good entry point for start-ups. Sometimes it might become a core part of the project, and grow considerably, reaching data complexity levels hard to query and maintain. This is where StrapiORM comes in. It allows any backend or frontend applications to treat Strapi as a database, and abstract the synchronization, filtering, population and sorting of the entities.
npm i @vicodes/strapi-orm
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.
You can use the synchronize
option of the Manager
to automatically create the entities from the Strapi API schema.
This will create all the entities, components, dynamic zones, relations and enums that are defined in the Strapi Content
Manager in the corresponding directories at the root of the project. Then they can be moved to the desired location and
next time the application runs, entities will be automatically detected and updated
To do it manually, 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
}
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)
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,
entities: ['src/**/*.entity.ts', 'src/**/*.component.ts'],
synchronize: 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', { $eq: 'read-only' })
.getMany()
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
}
}
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 |
entities | string, string[] | glob pattern that matches the entities and components (e.g.: src/**/*.entitiy.ts ) |
true | |
validateSchema | boolean | either to validate or not the existing entities against the Strapi API schema using URI /api/content-manager/content-types |
false | false |
synchronize | boolean | either to synchronize or not the existing entities against the Strapi API schema using URI /api/content-manager/content-types . Only enable this options if you want your entities to automatically update when there is a change in the database. Only enable this options in non-production environments |
false | false |
Returns a new Repository instance for the given entity.
- Return all entities
- 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.
- Finds an entity by its id.
- Same as `#findBy` but it will return only one element. The first in the list
- Creates a new entity.
- Updates an entity by its id.
- Deletes an entity by its id.
- Creates a new `StrapiQueryBuilder` instance for the given 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.
- 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
- Executes the query and returns the entities.
- Executes the query and returns the first entity.