Database Repositories
Formidable includes a repository layer that abstracts the database layer. This layer is built on top of the Knex.js query builder.
Creating Repositories
To generate a repository, execute the make:repository Craftsman command. All repositories generated by the framework will be placed in the app/Repositories directory:
node craftsman make:repository UserRepository
A repository contains one export by default: Repository. This export returns a class that extends the Repository class. The Repository class provides a few helper methods to make it easier to interact with the database.
Here's an example of a UserRepository:
- Imba
- TypeScript
import { Repository } from '@formidablejs/framework'
export class UserRepository < Repository
# The table associated with the repsitory.
get tableName
'users'
# Context reference.
static get context\string
'UserRepository'
import { Repository } from '@formidablejs/framework'
export class UserRepository extends Repository {
/**
* The table associated with the repsitory.
*/
get tableName(): string {
return 'users'
}
/**
* Context reference.
*/
static get context(): string {
return 'UserRepository'
}
}
Database Repository Conventions
Repositories generated by the make:repository Craftsman command are placed in the app/Repositories directory. Within this directory, each repository is stored in a directory that matches the "namespace" of the repository. So, a UserRepository class is stored at app/Repositories/UserRepository.{ts,imba}.
Table Names
By default, the Repository class will assume the table name of the repository matches the "snake case" plural name of the repository class. So, for example, the UserRepository class would assume the users table and TeamMemberRepository would assume the team_members table.
You may override this convention by defining a tableName getter on the repository class:
- Imba
- TypeScript
import { Repository } from '@formidablejs/framework'
export class UserRepository < Repository
# The table associated with the repsitory.
get tableName\string
'users'
# Context reference.
static get context\string
'UserRepository'
import { Repository } from '@formidablejs/framework'
export class UserRepository extends Repository {
/**
* The table associated with the repsitory.
*/
get tableName(): string {
return 'users'
}
/**
* Context reference.
*/
static get context(): string {
return 'UserRepository'
}
}
Primary Keys
By default, the Repository class will assume the primary key of the repository's table is an auto-incrementing integer named id. You may override this convention by defining a primaryKey static getter on the repository class:
- Imba
- TypeScript
import { Repository } from '@formidablejs/framework'
export class UserRepository < Repository
# Context reference.
static get context\string
'UserRepository'
# Primary key.
static get primaryKey\string
'uuid'
import { Repository } from '@formidablejs/framework'
export class UserRepository extends Repository {
/**
* Context reference.
*/
static get context(): string {
return 'UserRepository'
}
/**
* Primary key.
*/
static get primaryKey(): string {
return 'uuid'
}
}
Route Keys
When doing Route Repository Binding, Formidable will dependency inject a repository record into a controller action based on the value of the route's URI segment. By default, Formidable uses id as the route key. You may override this convention by defining a routeKeyName getter on the repository class:
- Imba
- TypeScript
import { Repository } from '@formidablejs/framework'
export class UserRepository < Repository
# Context reference.
static get context\string
'UserRepository'
# Route key name.
get routeKeyName\string
'uuid'
import { Repository } from '@formidablejs/framework'
export class UserRepository extends Repository {
/**
* Context reference.
*/
static get context(): string {
return 'UserRepository'
}
/**
* Route key name.
*/
get routeKeyName(): string {
return 'uuid'
}
}
Lets take a look at an example. If you have a route defined like this:
- Imba
- TypeScript
import { Route } from '@formidablejs/framework'
import { UserController } from '../app/Http/Controllers/UserController'
Route.get('users/:id', [UserController, 'show'])
import { Route } from '@formidablejs/framework'
import { UserController } from '../app/Http/Controllers/UserController'
Route.get('users/:id', [UserController, 'show'])
And you have a controller like this:
- Imba
- TypeScript
import { Controller, response } from '@formidablejs/framework'
import { UserRepository } from '../../Repositories/UserRepository'
export class UserController < Controller
@user(UserRepository)
def show user
response user
import { Controller, response } from '@formidablejs/framework'
import { UserRepository } from '../../Repositories/UserRepository'
export class UserController extends Controller {
@user(UserRepository)
async show(user: user): Promise {
return response(await user)
}
}
The user parameter will be a record from the users table where the id column matches the value of the id URI segment. However, if you change the routeKeyName to uuid on the UserRepository, the user parameter will be a record from the users table where the uuid column matches the value of the id URI segment.
Querying
The Repository class provides a variety of helpful methods that may be used to build database queries. In addition to these methods, you may also use the Knex query builder to write queries by accessing the query property on a repository instance:
UserRepository.query().where('active', true).orderBy('created_at', 'desc').get()
get
The get method returns all the records from the repository's table:
UserRepository.get()
The get method will also work with any query builder instance:
UserRepository.where('active', true).get()
first
The first method returns the first record from the repository's table:
UserRepository.first()
The first method may also be used to retrieve the first record matching the given query conditions:
UserRepository.where('active', true).first()
find
The find method returns the record matching the given primary key value. If no matching record exists, it returns null.
UserRepository.find(12)
where
The where method may be used to add WHERE clauses to the query. The method accepts an object of key-value pairs, where the keys are the column names and the values are the values to be compared against.
In addition to comparing primitive values, you may also compare a column against another column:
UserRepository.where('created_at', '>', 'updated_at')
You may also chain multiple where methods to create more specific queries:
UserRepository.where('active', true).where('created_at', '>', 'updated_at')
whereNot
The whereNot method verifies that the value of the given column is not equal to the given value:
UserRepository.whereNot('active', true)
whereIn
The whereIn method verifies that a given column's value is contained within the given array:
UserRepository.whereIn('id', [1, 2, 3])
whereNull
The whereNull method verifies that the value of the given column is NULL:
UserRepository.whereNull('updated_at')
whereNotNull
The whereNotNull method verifies that the value of the given column is not NULL:
UserRepository.whereNotNull('updated_at')
whereExists
The whereExists method filters the query based on the existence of any records in the subquery:
UserRepository.whereExists(function (query) {
query.select('*').from('accounts').whereRaw('users.account_id = accounts.id')
})
whereNotExists
The whereNotExists method filters the query based on the non-existence of any records in the subquery:
UserRepository.whereNotExists(function (query) {
query.select('*').from('accounts').whereRaw('users.account_id = accounts.id')
})
whereBetween
The whereBetween method verifies that a column's value is between two values:
UserRepository.whereBetween('id', [1, 100])
whereNotBetween
The whereNotBetween method verifies that a column's value lies outside of two values:
UserRepository.whereNotBetween('id', [1, 100])
whereRaw
The whereRaw method may be used to inject a raw WHERE clause into your query:
UserRepository.whereRaw('id = ?', [1])
whereLike
The whereLike method may be used to add a where clause with case-sensitive substring comparison on a given column with a given value:
UserRepository.whereLike('name', 'John')
whereILike
The whereILike method may be used to add a where clause with case-insensitive substring comparison on a given column with a given value:
UserRepository.whereILike('name', 'John')
whereJsonObject
The whereJsonObject method may be used to add a where clause with json object comparison on given json column:
UserRepository.whereJsonObject('options', { active: true })
whereJsonPath
The whereJsonPath method may be used to add a where clause with comparison of a value returned by a JsonPath given an operator and a value:
UserRepository.whereJsonPath('profile', '$.age', '>', 18)
whereJsonSupersetOf
The whereJsonSupersetOf method may be used to add a where clause where the comparison is true if a json given by the column include a given value. Only on MySQL, PostgreSQL and CockroachDB:
UserRepository.whereJsonSupersetOf('profile', { age: 18 })
whereJsonSubsetOf
The whereJsonSubsetOf method may be used to add a where clause where the comparison is true if a json given by the column is included in a given value. Only on MySQL, PostgreSQL and CockroachDB:
UserRepository.whereJsonSubsetOf('profile', { age: 18 })
groupBy
The groupBy method may be used to group the query results by a given column:
UserRepository.groupBy('team_id')
groupByRaw
The groupByRaw method may be used to group the query results by a raw SQL expression:
UserRepository.groupByRaw('extract(year from created_at)')
orderBy
The orderBy method allows you to sort the result of the query by a given column. The first argument to the orderBy method should be the column you wish to sort by, while the second argument controls the direction of the sort and may be either asc or desc:
UserRepository.orderBy('name', 'desc')
You may also sort by multiple columns:
UserRepository.orderBy({
column: 'name',
order: 'desc'
}, {
column: 'email',
order: 'asc'
})
orderByRaw
The orderByRaw method may be used to sort the query results by a raw SQL expression:
UserRepository.orderByRaw('updated_at - created_at DESC')
having
The having method's signature is similar to that of the where method:
UserRepository.having('email', '>', 100)
havingRaw
The havingRaw method may be used to inject a raw HAVING clause into your query:
UserRepository.havingRaw('id = ?', [1])
havingIn
The havingIn method may be used to add a HAVING IN clause to the query:
UserRepository.havingIn('id', [1, 2, 3])
havingNotIn
The havingNotIn method may be used to add a HAVING NOT IN clause to the query:
UserRepository.havingNotIn('id', [1, 2, 3])
havingBetween
The havingBetween method may be used to add a HAVING BETWEEN clause to the query:
UserRepository.havingBetween('id', [1, 100])
havingNotBetween
The havingNotBetween method may be used to add a HAVING NOT BETWEEN clause to the query:
UserRepository.havingNotBetween('id', [1, 100])
min
The min method allows you to retrieve the minimum value of a given column:
UserRepository.min('age')
max
The max method allows you to retrieve the maximum value of a given column:
UserRepository.max('age')
sum
The sum method allows you to retrieve the sum of a given column:
LikeRepository.sum('count')
sumDistinct
The sumDistinct method allows you to retrieve the sum of a given column with distinct values:
LikeRepository.sumDistinct('count')
avg
The avg method allows you to retrieve the average of a given column:
LikeRepository.avg('count')
avgDistinct
The avgDistinct method allows you to retrieve the average of a given column with distinct values:
LikeRepository.avgDistinct('count')
offset
The offset method skips the given number of results:
UserRepository.offset(10)
limit
The limit method limits the number of results returned from the query:
UserRepository.limit(10)
count
The count method returns the total number of records matching the query constraints:
UserRepository.count()
countDistinct
The countDistinct method returns the total number of distinct records matching the query constraints:
UserRepository.countDistinct()
softDelete
The softDelete method marks the given records as deleted. Soft deleted records are not actually removed from your database. Instead, a deleted_at column is set on the record. When querying a repository that uses soft deletes, the soft deleted records will be excluded from the results:
UserRepository.where('active', false).softDelete()
restore
The restore method restores the given records. Restored records will have their deleted_at column set to null:
UserRepository.where('active', false).restore()
withTrashed
The withTrashed method may be used to include soft deleted records in the results:
UserRepository.withTrashed().where('active', false).get()
withoutTrashed
The withoutTrashed method may be used to only include records that are not soft deleted:
UserRepository.withoutTrashed().where('active', false).get()
onlyTrashed
The onlyTrashed method may be used to only retrieve soft deleted records:
UserRepository.onlyTrashed().where('active', false).get()
Pagination
You may paginate query results using the pagination or autoPaginate methods. Both methods return an object with the data and pagination properties. The data property contains the query results and the pagination property contains the pagination information.
The pagination method, paginates the query results:
UserRepository.pagination({
page: 1,
perPage: 10,
query: {},
url: ''
})
| Property | Type | Description |
|---|---|---|
| page | number | The current page. The default value is 1. |
| perPage | number or null | The number of records per page. The default value is 20. |
| query | object or null | The query object of the request. |
| url | string or null | The url of the request. |
Or you can use the autoPaginate method:
UserRepository.autoPaginate(15)
The autoPaginate method accepts the number of records per page as the first argument. The default value is 20.
Here's an example response:
{
"data": [
{
"id": 1001,
"name": "Dr. Maureen Cormier",
"email": "Tyrel_Emard@gmail.com",
"password": "$2b$10$bqL4Z.gWsEMevmNDWtP0YOu03YOXREiCbfOktXSjK85JKGjY/RP2K",
"remember_token": null,
"email_verified_at": null,
"created_at": "2023-12-16 05:58:26",
"updated_at": "2023-12-16 05:58:26"
},
...
],
"pagination": {
"total": 3000,
"pageSize": 10,
"currentPage": 1,
"totalPages": 300,
"pages": [1, 2, 3, 4, 5],
"firstPage": 1,
"lastPage": 300,
"prevPage": null,
"nextPage": 2,
"links": {
"1": {
"label": 1,
"active": true,
"url": "http://127.0.0.1:3000/api/users/?value=&page=1"
},
"2": {
"label": 2,
"active": false,
"url": "http://127.0.0.1:3000/api/users/?value=&page=2"
},
"3": {
"label": 3,
"active": false,
"url": "http://127.0.0.1:3000/api/users/?value=&page=3"
},
"4": {
"label": 4,
"active": false,
"url": "http://127.0.0.1:3000/api/users/?value=&page=4"
},
"5": {
"label": 5,
"active": false,
"url": "http://127.0.0.1:3000/api/users/?value=&page=5"
},
"firstPage": {
"label": "First",
"active": true,
"url": "http://127.0.0.1:3000/api/users/?value=&page=1"
},
"prevPage": null,
"nextPage": {
"label": "Next",
"active": false,
"url": "http://127.0.0.1:3000/api/users/?value=&page=2"
},
"lastPage": {
"label": "Last",
"active": false,
"url": "http://127.0.0.1:3000/api/users/?value=&page=300"
}
}
}
}