| id | crud |
|---|---|
| title | CRUD Operations |
| description | Documentation for out-of-the-box CRUD endpoints with Manifest. Paginated lists, detail views, creating and updating single or collection entities. |
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
Once you created your entities, you probably want to interact with them. It is easy to connect your client to your Manifest backend.
Manifest provides out-of-the-box CRUD endpoints through the REST API or the JS SDK.
:::info
By default CRUD endpoints are private, only accessible for logged-in admin users. You can open them to the public using policies.
:::
Manifest exposes a REST API for CRUD operations. The OpenAPI documentation is automatically generated and the UI is available at http://localhost:1111/api. Have a look!
Anopenapi.yml file is also generated along a types.ts file in the ./manifest folder. Those 2 files are an amazing source of context for your LLM. If you want to connect a frontend to your Manifest backend, make sure that your AI coding tool sees those files to simplify your development.
For CRUD endpoints, this prefix is followed by collections for collections entities and singles for single entities and by the slug of your entity (you can change it in the entity params)
Examples:
http://localhost:1111/api/collections/catsgets the list of the catshttp://localhost:1111/api/singles/home-contentgets the home content
:::tip
In addition to CRUD endpoints that are generated automatically, you also can create your own custom endpoints to add your custom logic.
:::
The Manifest JS SDK is used to fetch and manipulate your data from your JS client using an elegant and human-friendly interface.
The SDK can be integrated in any frontend stack app like React, Vue, Svelte, Astro, Angular.... Or even by another server using NodeJS!
Install it via the terminal:
npm i @mnfst/sdkUse the SDK directly in your favorite frontend:
import Manifest from '@mnfst/sdk'
// Initialize client with default backend URL: http://localhost:1111.
const manifest = new Manifest()
// Initialize client with custom base URL.
const manifest = new Manifest('https://example.com')
// Perform CRUD operations...
const cats = await manifest.from('cats').find()The following CRUD operations can be done on collections entities. A collection is a list of items that share a similar schema.
This operation will fetch a list of items from a collection.
Request URL: GET /api/collections/:slug
GET /api/collection/users{
"data": [
{
"id": "2c4e6a8b-0d1f-4357-9ace-bdf024681357",
"name": "Lara"
},
{
"id": "e4d5c6b7-a890-4123-9876-543210fedcba",
"name": "Karl"
}
],
"currentPage": 1,
"lastPage": 1,
"from": 1,
"to": 10,
"total": 3,
"perPage": 10
}List filters
You can filter by property to refine the list of items. Use suffix to pass logic to it:
| Suffix | Description | Example |
|---|---|---|
| _eq | equals | isActive_eq=true |
| _neq | not equals | name_neq=alice |
| _gt | greater than | birthdate_gt=2020-01-01 |
| _gte | greater than or equal | age_gte=4 |
| _lt | less than | amount_lt=400 |
| _lte | less than or equal | amount_lte=400 |
| _like | like | name_like=%bi% |
| _in | included in | customer_in=1,2,3 |
Pagination
All list requests are paginated by default. Just use the page parameter to choose your page and the perPage param if you want to change the number of items per page.
| Param | Description | Example |
|---|---|---|
| page | The number of the page requested | page=3 |
| perPage | The number of items of each page | perPage=40 |
Order
Order your list by a defined property. By default the results are ordered by id in a DESC order and thus shows the new ones first.
| Param | Description | Example |
|---|---|---|
| orderBy | The name of property you want to order by. | orderBy=age |
| order | Ascending 'ASC' or Descending 'DESC' | order=DESC |
console.log(users);
// Output: {
// "data": [
// {
// "id": 'e4d5c6b7-a890-4123-9876-543210fedcba',
// "name": "Lara"
// },
// {
// "id": '2c4e6a8b-0d1f-4357-9ace-bdf024681357',
// "name": "Karl"
// }
// ],
// "currentPage": 1,
// "lastPage": 1,
// "from": 1,
// "to": 10,
// "total": 3,
// "perPage": 10
// }
```
List filters
You can filter by property to refine the list of items. Use the where() function with the correct operator to do it.
const cats = await manifest
.from('cats')
.where('breed = siamese')
.andWhere('active = true')
.andWhere('birthDate > 2020-01-01')
.find()Filter operators
| Operator | Description | Example |
|---|---|---|
| = | equals | .where('isActive = true') |
| != | not equals | .where('name != alice') |
| > | greater than | .where('birthdate > 2020-01-01') |
| >= | greater than or equal | .where('age >= 4') |
| < | less than | .where('amount < 400') |
| <= | less than or equal | .where('amount <= 400') |
| like | like | .where('name_like=%bi%') |
| in | included in | .where('customer_in=1,2,3') |
Pagination
All list requests are paginated by default. Just use the page parameter to chose your page and the perPage param if you want to change the number of items per page.
const cats = await manifest.from('cats').find({ page: 1, perPage: 10 })Order
Order your list by a defined property. By default the results are ordered by id in a DESC order and thus shows the new ones first.
// Order cats.
const cats = await manifest.from('cats').orderBy('age', { desc: true }).find()This operation will fetch a single item based on its ID.
**Request URL**: `GET /api/collections/:slug/:id````http title="Example HTTP Request"
GET /api/collections/cats/2c4e6a8b-0d1f-4357-9ace-bdf024681357
```
```json title="Example HTTP Response"
{
"name": "Mina",
"description": "A really cute cat"
}
```
console.log(cat);
// Output: {
// name: "Mina",
// description: "A really cute cat"
// }
```
This operation will create a new item and store it in the database. The newly created item is returned as response.
Request URL: POST /api/collections/:slug
```http title="Example HTTP Request"
POST /api/collections/pokemons
Content-Type: application/json
Body:
{
"name": "Pikachu",
"type": "electric",
"level": 3,
}
```
```json title="Example HTTP Response"
{
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"name": "Pikachu",
"type": "electric",
"level": 3,
}
```
console.log(newPokemon);
// Output: {
// id: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
// name: "Pikachu",
// type: "electric",
// level: 3
// }
```
This operation will replace an existing item by the payload provided in the request and returns the updated item.
Unlike partial updates, this operation will replace the whole item by the new one. Missing or empty properties will delete the previous ones.
**Request URL**: `PUT /api/collections/:slug/:id````http title="Example HTTP Request"
PUT /api/collections/pokemons/6ba7b810-9dad-11d1-80b4-00c04fd430c8
Content-Type: application/json
Body:
{
"name": "Raichu",
"type": "electric",
"level": 8
}
```
```json title="Example HTTP Response"
{
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"name": "Raichu",
"type": "electric",
"level": 8
}
```
console.log(newPokemon);
// Output: {
// id: "a1b2c3d4-e5f6-4789-abcd-ef0123456789",
// name: "Raichu",
// type: "electric",
// level: 8
// }
```
This operation will partially replace an existing item and return the updated item.
Unlike fully replacement, this operation will only modify the properties provided in the payload and leave the other ones as they are.
Request URL: PATCH /api/collections/:slug/:id
```http title="Example HTTP Request"
PATCH /api/collections/pokemons/a1b2c3d4-e5f6-4789-abcd-ef0123456789
Content-Type: application/json
Body:
{
"level": 5,
}
```
```json title="Example HTTP Response"
{
"id": "a1b2c3d4-e5f6-4789-abcd-ef0123456789",
"name": "Pikachu",
"type": "electric",
"level": 5
}
```
console.log(newPokemon);
// Output: {
// id: "a1b2c3d4-e5f6-4789-abcd-ef0123456789",
// name: "Pikachu",
// type: "electric",
// level: 5
}
```
This operation will delete permanently an item from the database. This is an irreversible action. The deleted item is returned in the response.
**Request URL**: `DELETE /api/collections/:slug/:id`DELETE api/collections/cats/550e8400-e29b-41d4-a716-446655440000
{
"name": "Fido",
"description": "A cute black cat"
}console.log(deletedCat);
// Output: {
// name: "Fido",
// description: "A cute black cat"
// }
````
The following operations can be done on singles entities. As there is only a single item per entity, there is no list-related operations, and single items cannot be deleted as we always want to return an object, even if empty.
**Request URL**: `GET /api/singles/:slug`GET /api/singles/homepage{
"title": "My title",
"description": "Welcome to my website!"
}console.log(homepage);
// Output: {
// title: "My title",
// description: "Welcome to my website!"
// }
````
This operation will replace an existing item by the payload provided in the request. Unlike partial updates, this operation will replace the whole item by the new one. Missing or empty properties will delete the previous ones.
Request URL: PUT /api/singles/:slug
PUT /api/singles/homepage
Content-Type: application/json
Body:
{
"title": "My new title",
"description": "My new description"
}
{
"title": "My new title",
"description": "My new description"
}console.log(newHomepage);
// Output: {
// title: "My new title",
// description: "My new description"
// }
```
This operation will partially replace an existing item. Unlike fully replacement, this operation will only modify the properties provided in the payload an leave the other ones as they are.
**Request URL**: `PATCH /api/singles/:slug`PATCH /api/singles/homepage
Content-Type: application/json
Body:
{
"title": "My new title",
}{
"title": "My new title",
"description": "Welcome to my website!"
}// Update single entity partially.
const homepage = await manifest.single('homepage').patch({
title: 'My new title'
})
console.log(homepage)
// Output: {
// title: "My new title",
// description: "Welcome to my website!"
// }If you added relationships between your entities, you probably want to include them in the CRUD operations.
You can specify which relations you want to load with your entities in your query. Eager relations are loaded automatically.
```js // Fetch entities with 2 relations. const cities = await manifest .from('cities') .with(['region', 'mayor']) .find() // Fetch nested relations.
const cities = await manifest
.from('cities')
.with(['region', 'region.country', 'region.country.planet'])
.find()
```
// Fetch entities with 2 relations.
GET http://localhost:1111/api/dynamic/city?relations=region,mayor
// Fetch nested relations.
GET http://localhost:111/api/dynamic/city?relations=region,region.country,region.country.planet
```
Once the relation is loaded, you can also filter items by their relation id or properties.
```js // Get all cats that belong to owner with id 3f2504e0-4f89-11d3-9a0c-0305e82c3301. const cats = await manifest .from('cats') .with(['owner']) .where('owner.id = 3f2504e0-4f89-11d3-9a0c-0305e82c3301') .find() // Get all cats that have an owner with name "Jorge".
const cats = await manifest
.from('cats')
.with(['owner'])
.where('owner.name = Jorge')
.find()
```
// Get all cats that have an owner with name "Jorge".
GET http://localhost:1111/api/dynamic/cats?relations=owner&owner.name_eq=Jorge
```
To store or update an item with its relations, you have to pass the relation id(s) as a property that end with Id for many-to-one and Ids for many-to-many like companyId or tagIds
:::note
When storing many-to-many relations, you always need to pass an array, even if you just have one single value.
:::
As for updating properties, you can either do a full replacement using the update function (PUT) or a partial replacement using the patch function (PATCH).
```http // Replaces the whole skill relations by the new skillIds array. PUT http://localhost:1111/api/dynamic/players/e4d5c6b7-a890-4123-9876-543210fedcba Content-Type: application/json { name: 'Mike', teamId: 'e4d5c6b7-a890-4123-9876-543210fedcba', skillIds: ['12345678-1234-5678-9abc-123456789012', '3f2504e0-4f89-11d3-9a0c-0305e82c3301'] }// Updates the team without changing the skills or the name.
PATCH http://localhost:1111/api/dynamic/players/e4d5c6b7-a890-4123-9876-543210fedcba
Content-Type: application/json
{
teamId: '9b2fff23-ec93-4b48-9322-bbd4b6b5b123',
}
```
// Updates the team without changing the skills or the name.
await manifest.from('players').patch('e4d5c6b7-a890-4123-9876-543210fedcba', {teamId: '9b2fff23-ec93-4b48-9322-bbd4b6b5b123'})
```