Skip to main content

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:

app/Repositories/UserRepository.ts
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:

app/Repositories/UserRepository.ts
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:

app/Repositories/UserRepository.ts
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:

app/Repositories/UserRepository.ts
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:

routes/api.ts
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:

app/Http/Controllers/UserController.ts
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()
info

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: ''
})
PropertyTypeDescription
pagenumberThe current page. The default value is 1.
perPagenumber or nullThe number of records per page. The default value is 20.
queryobject or nullThe query object of the request.
urlstring or nullThe 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"
}
}
}
}