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"
}
}
}
}