grapher/docs/introduction.md

214 lines
5.8 KiB
Markdown
Raw Normal View History

2017-12-01 13:11:06 +02:00
# Welcome to Grapher!
2017-11-30 22:11:43 +02:00
## The 3 Modules
Grapher is composed of 3 main modules, that work together:
2017-12-01 13:11:06 +02:00
#### Link Manager
2017-12-01 12:30:09 +02:00
This module allows you to configure relationships between collections and allows you to create denormalized links.
2017-11-30 22:11:43 +02:00
2017-12-01 13:11:06 +02:00
#### Query
2017-11-30 22:11:43 +02:00
The query module is used for fetching your data in a friendly manner, such as:
```js
createQuery({
users: {
firstName: 1
}
})
```
2017-12-01 12:30:09 +02:00
It abstracts your query into a graph composed of Collection Nodes and Field Nodes.
2017-11-30 22:11:43 +02:00
it uses the **Link Manager** to construct this graph and if the fetching is done server-side (non-reactive queries),
it uses the **Hypernova Module** the crown jewl of Grapher, which heavily minimizes requests to database.
2017-12-01 13:11:06 +02:00
#### Exposure
2017-11-30 22:11:43 +02:00
The exposure represents the layer between your queries and the client, allowing you to securely expose your queries,
only to users that have access.
2017-12-01 13:11:06 +02:00
## Let's begin!
2017-11-30 22:11:43 +02:00
2017-12-01 12:30:09 +02:00
You can use Grapher without defining any links, for example, let's say you have a method which returns a list of posts.
2017-11-30 22:11:43 +02:00
```js
2017-12-01 13:11:06 +02:00
const Posts = new Mongo.Collection('posts');
2017-11-30 22:11:43 +02:00
Meteor.methods({
posts() {
return Posts.find({}, {
fields: {
title: 1,
createdAt: 1,
createdBy: 1,
}
}).fetch();
}
})
```
2017-12-01 12:30:09 +02:00
Transforming this into a Grapher query looks like this:
2017-11-30 22:11:43 +02:00
```js
Meteor.methods({
posts() {
const query = Posts.createQuery({
title: 1,
createdAt: 1,
createdBy: 1,
});
return query.fetch();
}
})
```
One of the advantages that Grapher has, is the fact that it forces you to specify the fields you need,
you may find this cumbersome in the beginning, but as your application grows, new fields are added,
fields that need to be protected, you'll find yourself refactoring parts of your code-base which exposed
all the fields.
2017-12-01 12:30:09 +02:00
If, for example, you want to filter or sort your query, we introduce the `$filters` and `$options` fields:
2017-11-30 22:11:43 +02:00
```js
Meteor.methods({
posts() {
// Previously Posts.find({isApproved: true}, {sort: '...', fields: '...'});
const query = Posts.createQuery({
$filters: {
isApproved: true,
},
$options: {
sort: {createdAt: -1}
},
title: 1,
createdAt: 1,
createdBy: 1,
});
return query.fetch();
}
})
```
If for example you are searching an element by `_id`, you may have `$filters: {_id: 'XXX'}`, then instead of `fetch()` you
can call `.fetchOne()` so it will return the first element found.
2017-12-01 13:11:06 +02:00
`$filters` and `$options` are the ones supported by [Mongo.Collection.find()](http://docs.meteor.com/api/collections.html#Mongo-Collection-find)
2017-12-01 12:30:09 +02:00
2017-12-01 13:11:06 +02:00
## Dynamic $filter()
2017-11-30 22:11:43 +02:00
2017-12-01 12:30:09 +02:00
The nature of a query is to be re-usable. For this we introduce a special type of field called `$filter`,
which allows the query to receive parameters and adapt before it executes:
2017-11-30 22:11:43 +02:00
```js
2017-12-01 12:30:09 +02:00
// We export the query, notice there is no .fetch()
2017-11-30 22:11:43 +02:00
export default Posts.createQuery({
2017-12-01 13:11:06 +02:00
$filter({filters, options, params}) {
2017-11-30 22:11:43 +02:00
filters.isApproved = params.isApproved;
2017-12-01 13:11:06 +02:00
},
$options: {sort: {createdAt: -1}},
title: 1,
createdAt: 1,
createdBy: 1,
2017-11-30 22:11:43 +02:00
});
```
2017-12-01 12:30:09 +02:00
The `$filter()` function receives a single object composed of 3 objects: `filters`, `options`, `params`.
2017-11-30 22:11:43 +02:00
The `filters` and `options` are initially what you provided in `$filters` and `$options` query, they will be empty
2017-12-01 12:30:09 +02:00
objects if they haven't been specified.
2017-11-30 22:11:43 +02:00
The job of `$filter()` is to extend/modify `filters` and `options`, based on params.
2017-12-01 12:30:09 +02:00
Lets see how we can re-use the query defined above:
2017-11-30 22:11:43 +02:00
```js
import postListQuery from '...';
Meteor.methods({
posts() {
return postListQuery.clone({
isApproved: true
}).fetch()
}
})
```
2017-12-01 12:30:09 +02:00
Whenever we want to use a modular query, we have to `clone()` it so it creates a new instance of it.
The `clone()` accepts `params` as argument. Those `params` will be passed to the `$filter` function.
2017-11-30 22:11:43 +02:00
2017-12-01 12:30:09 +02:00
You can also use `setParams()` to configure parameters, which extends the current query parameters:
2017-11-30 22:11:43 +02:00
```js
import postListQuery from '...';
Meteor.methods({
posts() {
const query = postListQuery.clone();
2017-12-01 12:30:09 +02:00
// Warning, if you don't use .clone() and you just .setParams(),
// those params will remain stored in your query
2017-11-30 22:11:43 +02:00
query.setParams({
isApproved: true,
});
return query.fetch();
}
})
```
2017-12-01 13:11:06 +02:00
## Validating Params
2017-12-01 12:30:09 +02:00
2017-11-30 22:11:43 +02:00
A query can be smart enough to know what parameters it needs, for this we can use the awesome `check` library from Meteor:
http://docs.meteor.com/api/check.html
```js
import {Match} from 'meteor/check';
export default Posts.createQuery({
$filter({filters, options, params}) {
filters.isApproved = params.isApproved;
if (params.authorId) {
filters.authorId = params.authorId;
}
},
...
}, {
validateParams: {
isApproved: Boolean,
authorId: Match.Maybe(String),
}
});
```
2017-12-01 12:30:09 +02:00
If you want to craft your own validation, it also accepts a function that takes params:
2017-11-30 22:11:43 +02:00
```js
{
validateParams(params) {
if (somethingIsWrong) {
throw new Meteor.Error('invalid-params', 'Explain why');
}
}
}
```
2017-12-01 12:30:09 +02:00
Note: params validation is done prior to fetching the query, not when you do `setParams()` or `clone()`
2017-11-30 22:11:43 +02:00
2017-12-01 12:30:09 +02:00
If you want to store some default parameters, you can use the `params` option:
2017-11-30 22:11:43 +02:00
```js
export default Posts.createQuery({...}, {
params: {
isApproved: true,
}
});
```
2017-12-01 12:58:36 +02:00
## Conclusion
2017-11-30 22:11:43 +02:00
This is the end of our introduction. As we can see, we can make queries modular and this already gives us
2017-12-01 12:30:09 +02:00
a big benefit. By abstracting them into their own modules we can keep our methods neat and clean.
2017-11-30 22:11:43 +02:00
2017-12-01 12:58:36 +02:00
## [Continue Reading](linking_collections.md) or [Back to Table of Contents](index.md)