Skip to content

Commit

Permalink
Feat: Profiles (#263)
Browse files Browse the repository at this point in the history
* refactor: move `ITEMS_PER_PAGE` to config file

* feat: add `ConfigProfile` definition

* feat: add `enabled` & `disabled` default profiles

* feat: add profile `loader`

* refactor: transform `FilterOption` & add `isAuthorized` method

* feat: implement `isAuthorized` for filter options

* feat: load profile on `QueryBuilder`

* fix: set `ENABLED_PROFILE` as default to avoid breaking changes

* feat: add `prettier` config file

* style: format files

* refactor: add new test including profile cases

* style: improve typings & remove deprecated `body-parser`

* test: add test for profile loader

* docs: add docs for `ConfigProfile`
  • Loading branch information
rjlopezdev authored Oct 8, 2021
1 parent 8a96f2f commit 43b9e04
Show file tree
Hide file tree
Showing 43 changed files with 1,060 additions and 1,265 deletions.
4 changes: 4 additions & 0 deletions .prettierrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
trailingComma: "es5"
tabWidth: 2
semi: false
singleQuote: true
116 changes: 80 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,28 @@
</p>

# TypeORM Express Query Builder

This library allows you to transfrom automatically Express.js _req.query_ into TypeORM findOptions queries.

## Installation

`npm install typeorm-express-query-builder`


## How it works?

![](https://raw.githubusercontent.com/rjlopezdev/typeorm-express-query-builder/master/typeorm-express-pipeline.png)


## Usage

Use QueryBuilder export from package and pass your `req.query` as an argument:

```typescript
import QueryBuilder from 'typeorm-express-query-builder';
import QueryBuilder from 'typeorm-express-query-builder'

const builder = new QueryBuilder(req.query);
const builtQuery = builder.build();
const builder = new QueryBuilder(req.query)
const builtQuery = builder.build()
// Now your query is built, pass it to your TypeORM repository
const results = await fooRepository.find(builtQuery);
const results = await fooRepository.find(builtQuery)
```

Given the following url query string:
Expand Down Expand Up @@ -76,10 +75,11 @@ It will be transformed into:
`GET foo/?name__contains=foo&role__in=admin,common&age__gte=18&page=3&limit=10`

`POST foo/?name__contains=foo&role__in=admin,common&age__gte=18&page=3&limit=10`

```javascript
app.get('/foo', (req, res) => {
const queryBuilder = new QueryBuilder(req.query); // => Parsed into req.query
const built = queryBuilder.build();
const queryBuilder = new QueryBuilder(req.query) // => Parsed into req.query
const built = queryBuilder.build()
})
```

Expand All @@ -97,48 +97,92 @@ POST foo/, body: {

```javascript
app.post('/foo', (req, res) => {
const queryBuilder = new QueryBuilder(req.body); // => Parsed into req.body
const built = queryBuilder.build();
const queryBuilder = new QueryBuilder(req.body) // => Parsed into req.body
const built = queryBuilder.build()
})
```

## Available Lookups

| Lookup | Behaviour | Example |
| --- | --- | --- |
_(none)_ | Return entries that match with value | `foo=raul`
__contains__ | Return entries that contains value | `foo__contains=lopez`
__startswith__ | Return entries that starts with value | `foo__startswith=r`
__endswith__ | Return entries that ends with value | `foo__endswith=dev`
__isnull__ | Return entries with null value | `foo__isnull`
__lt__ | Return entries with value less than or equal to provided | `foo__lt=18`
__lte__ | Return entries with value less than provided | `foo__lte=18`
__gt__ | Returns entries with value greater than provided | `foo__gt=18`
__gte__ | Return entries with value greater than or equal to provided | `foo__gte=18`
__in__ | Return entries that match with values in list | `foo__in=admin,common`
__between__ | Return entries in range | `foo__between=1,27`
| Lookup | Behaviour | Example |
| -------------- | ----------------------------------------------------------- | ---------------------- |
| _(none)_ | Return entries that match with value | `foo=raul` |
| **contains** | Return entries that contains value | `foo__contains=lopez` |
| **startswith** | Return entries that starts with value | `foo__startswith=r` |
| **endswith** | Return entries that ends with value | `foo__endswith=dev` |
| **isnull** | Return entries with null value | `foo__isnull` |
| **lt** | Return entries with value less than or equal to provided | `foo__lt=18` |
| **lte** | Return entries with value less than provided | `foo__lte=18` |
| **gt** | Returns entries with value greater than provided | `foo__gt=18` |
| **gte** | Return entries with value greater than or equal to provided | `foo__gte=18` |
| **in** | Return entries that match with values in list | `foo__in=admin,common` |
| **between** | Return entries in range | `foo__between=1,27` |

**Notice**: you can use negative logic prefixing lookup with `__not`.

*Example:*
_Example:_
`foo__not__contains=value`

## Options

### Pagination
| Option | Default | Behaviour | Example |
| --- | :---: | --- | --- |
pagination | __true__ | If _true_, paginate results. If _false_, disable pagination | `pagination=false`
page | __1__ | Return entries for page `page` | `page=2`
limit | __25__ | Return entries for page `page` paginated by size `limit` | `limit=15`

| Option | Default | Behaviour | Example |
| ---------- | :------: | ----------------------------------------------------------- | ------------------ |
| pagination | **true** | If _true_, paginate results. If _false_, disable pagination | `pagination=false` |
| page | **1** | Return entries for page `page` | `page=2` |
| limit | **25** | Return entries for page `page` paginated by size `limit` | `limit=15` |

### Ordering
| Option | Default | Behaviour | Example |
| --- | :---: | --- | --- |
order | - | Order for fields:<br>`+`: Ascendant <br> `-`: Descendant | `order=+foo,-name,+surname`

| Option | Default | Behaviour | Example |
| ------ | :-----: | -------------------------------------------------------- | --------------------------- |
| order | - | Order for fields:<br>`+`: Ascendant <br> `-`: Descendant | `order=+foo,-name,+surname` |

### Selection
| Option | Default | Behaviour | Example |
| --- | :---: | --- | --- |
| select | - | Fields to select as response. If no provided, it select all fields. | `select=name,surname,foo.nested`|
| with | - | Entity relations to attach to query | with=posts,comments

| Option | Default | Behaviour | Example |
| ------ | :-----: | ------------------------------------------------------------------- | -------------------------------- |
| select | - | Fields to select as response. If no provided, it select all fields. | `select=name,surname,foo.nested` |
| with | - | Entity relations to attach to query | `with=posts,comments` |

## Profile

If you need to disable some capabilities, you can do using shortcuts to `enable|disable` by default or provide a custom Profile.

A Profile describe capabilities that can be used by clients & its behaviour.

```typescript
const qb = new QueryBuilder(req.query, 'enabled' | 'disabled' | ConfigProgile)
```

### ConfigProfile

`ConfigProfile` object looks like:

```typescript
const customProfile: ConfigProfile = {
options: {
pagination: {
status: 'enabled',
paginate: true,
itemsPerPage: 25,
},
ordering: {
status: 'enabled',
},
relations: {
status: 'enabled',
},
select: {
status: 'enabled',
},
},
policy: 'skip',
}
```

| Field | Default | Behaviour | Type |
| ------- | :-------: | ---------------------------------------------------------- | ---------------- |
| options | 'enabled' | Profile options | `ProfileOptions` |
| policy | 'skip' | Policy to apply in cases client try use `disabled` options | `FindPolicyType` |
121 changes: 25 additions & 96 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
"@types/express": "^4.17.13",
"@types/jest": "^27.0.2",
"@types/node": "^16.9.6",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^4.31.2",
"@typescript-eslint/parser": "^4.31.2",
"body-parser": "^1.19.0",
"codecov": "^3.8.3",
"eslint": "^7.32.0",
"express": "^4.17.1",
Expand Down
1 change: 0 additions & 1 deletion src/default-config.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/express-query.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export type ExpressQuery = Record<string, any>;
export type ExpressQuery = Record<string, any>
Loading

0 comments on commit 43b9e04

Please sign in to comment.