1648666800
Normalize GraphQL responses for persisting in the client cache/state.
Not related, in any way, to normalizr, just shamelessly piggybacking on its popularity. Also, "normalizEr" is taken...
TL;DR: Transforms:
{
"data": {
"findUser": [
{
"__typename": "User",
"id": "5a6efb94b0e8c36f99fba013",
"email": "Lloyd.Nikolaus@yahoo.com",
"posts": [
{
"__typename": "BlogPost",
"id": "5a6efb94b0e8c36f99fba016",
"title": "Dolorem voluptatem molestiae",
"comments": [
{
"__typename": "Comment",
"id": "5a6efb94b0e8c36f99fba019",
"message": "Alias quod est voluptatibus aut quis sunt aut numquam."
},
{
"__typename": "Comment",
"id": "5a6efb94b0e8c36f99fba01b",
"message": "Harum quia asperiores nemo."
},
{
"__typename": "Comment",
"id": "5a6efb94b0e8c36f99fba01c",
"message": "Vel veniam consectetur laborum."
},
{
"__typename": "Comment",
"id": "5a6efb94b0e8c36f99fba01e",
"message": "Possimus beatae vero recusandae beatae quas ut commodi laboriosam."
}
]
}
]
}
]
}
}
into:
{
"comments": {
"5a6efb94b0e8c36f99fba019": {
"id": "5a6efb94b0e8c36f99fba019",
"message": "Alias quod est voluptatibus aut quis sunt aut numquam."
},
"5a6efb94b0e8c36f99fba01b": {
"id": "5a6efb94b0e8c36f99fba01b",
"message": "Harum quia asperiores nemo."
},
"5a6efb94b0e8c36f99fba01c": {
"id": "5a6efb94b0e8c36f99fba01c",
"message": "Vel veniam consectetur laborum."
},
"5a6efb94b0e8c36f99fba01e": {
"id": "5a6efb94b0e8c36f99fba01e",
"message": "Possimus beatae vero recusandae beatae quas ut commodi laboriosam."
}
},
"blogPosts": {
"5a6efb94b0e8c36f99fba016": {
"id": "5a6efb94b0e8c36f99fba016",
"title": "Dolorem voluptatem molestiae",
"comments": [
"5a6efb94b0e8c36f99fba019",
"5a6efb94b0e8c36f99fba01b",
"5a6efb94b0e8c36f99fba01c",
"5a6efb94b0e8c36f99fba01e"
]
}
},
"users": {
"5a6efb94b0e8c36f99fba013": {
"id": "5a6efb94b0e8c36f99fba013",
"email": "Lloyd.Nikolaus@yahoo.com",
"posts": ["5a6efb94b0e8c36f99fba016"]
}
}
}
We all love GraphQL and we want to use it. There are tons of libraries and clients out there that help us do that with ease, but there is still one problem... How do you persist that data?
Yes, everything is all great when the response mirrors the exact structure we asked for, but we don't want to cache it that way, do we? We probably want a normalized version of that data which we can persist to our store and read/modify it efficiently. Flux or Redux stores work best with normalized data and there are also GraphQL clients you can use to execute queries on the local cache/state (blips or apollo-link-state), in which case, we definitely need to persist normalized data.
GraphQLNormalizr is simple, fast, light-weight and it provides all the tools needed to do just that, the only requirement is that you include the id
and __typename
fields for all the nodes (but it can do that for you if you're too lazy or you want to keep your sources thin).
npm install graphql-normalizr
The GraphQLNormalizr constructor function returns an object containing 3 methods:
Depending on how you write your queries, you may or may not use parse
or addRequiredFields
, but normalize
is the method that you will transform the GraphQL response. As you've probably seen from the TL;DR, all response nodes must contain the __typename
and id
fields. __typename
is a GraphQL meta field and the id
key may be customized when creating the GraphQLNormalizr client.
If your queries already ask for id
and __typename
there's no need to use parse or addRequiredFields. Otherwise, parse will take care of transforming your GraphQL source
into a Document
and add the __typename
and id
fields where needed. In case you already use a different parser, or only have access to the Document
you may use addRequiredFields on the Document
to add the __typename
and id
fields
import { GraphQLNormalizr } from 'graphql-normalizr'
// const config = ...
const normalizer = new GraphQLNormalizr(config)
config: optional - the configuration object containing information for instantiating the client. it takes the following props:
idKey
String
Default is "id". Configures a custom id
key for the client. Use this if your resource identifiers are found under a different key name ('_id', 'key', 'uid' etc).
Consider the following GraphQL response:
const response = {
data: {
findUser: {
__typename: 'User',
uid: '5a6efb94b0e8c36f99fba013',
email: 'Lloyd.Nikolaus@yahoo.com',
},
},
}
Normalize the data with our custom id
key:
// using destructuring to get the `normalize` method of the client
const { normalize } = new GraphQLNormalizr({ idKey: 'uid' })
normalize(response)
// =>
// {
// users: {
// '5a6efb94b0e8c36f99fba013' : {
// uid: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// }
// }
useConnections
Boolean
Default is false
. If you are using GraphQL connections with edges
and nodes
, set this flag to true
otherwise you'll get a warning and the normalization won't work.
NOTE: The connections implementation needs to be according to the specification
const response = {
data: {
findUser: {
__typename: 'User',
id: '5a6efb94b0e8c36f99fba013',
email: 'Lloyd.Nikolaus@yahoo.com',
friends: {
__typename: 'FriendsConnection',
totalCount: 3,
edges: [
{
node: {
__typename: 'User',
id: '5a6cf127c2b20834f6551481',
email: 'Madisen_Braun@hotmail.com',
},
cursor: 'Y3Vyc29yMg==',
},
{
node: {
__typename: 'User',
id: '5a6cf127c2b20834f6551482',
email: 'Robel.Ansel@yahoo.com',
},
cursor: 'Y3Vyc29yMw==',
},
],
pageInfo: {
endCursor: 'Y3Vyc29yMw==',
hasNextPage: false,
},
},
},
},
}
const { normalize } = new GraphQLNormalizr({
useConnections: true,
})
normalize(response)
// =>
// {
// users: {
// '5a6efb94b0e8c36f99fba013': {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com',
// friends: ['5a6cf127c2b20834f6551481', '5a6cf127c2b20834f6551482'],
// },
// '5a6cf127c2b20834f6551481': {
// id: '5a6cf127c2b20834f6551481',
// email: 'Madisen_Braun@hotmail.com',
// },
// '5a6cf127c2b20834f6551482': {
// id: '5a6cf127c2b20834f6551482',
// email: 'Robel.Ansel@yahoo.com',
// },
// },
// }
typeMap
Object
By default the entity name will be the plural form of the type name, converted to camel case, (PrimaryAddress
type will be stored under the primaryAddresses
key). Use this option to provide specific entity names for some/all Types, or try the plural and casing options to derive the entity names.
const response = {
data: {
findUser: {
__typename: 'User',
id: '5a6efb94b0e8c36f99fba013',
email: 'Lloyd.Nikolaus@yahoo.com',
},
},
}
const { normalize } = new GraphQLNormalizr({
typeMap: { User: 'accounts' },
})
normalize(response)
// =>
// {
// accounts: {
// '5a6efb94b0e8c36f99fba013' : {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// }
// }
exclude
Object
Prevent normalization of specified fields
const response = {
data: {
allUsers: [
{
__typename: 'User',
id: '5a6efb94b0e8c36f99fba013',
email: 'Lloyd.Nikolaus@yahoo.com',
preferences: null
posts: [
{
__typename: 'BlogPost',
id: '5a6cf127c2b20834f6551484',
likes: 10,
title: 'Sunt ut aut',
tags: {},
}
]
},
{
__typename: 'User',
id: '5a6efb94b0e8c36f99fba013',
email: 'Anna.Klaus@gmail.com',
preferences: { foo: 'apple', bar: 1, baz: { a: 'b' }, quux: null, }
posts: [
{
__typename: 'BlogPost',
id: '5a6cf127c2b20834f6551485',
likes: 23,
title: 'Nesciunt esse',
tags: [],
}
]
},
],
},
}
Normalize the data excluding the preferences
field on users
and the tags
field on blogPosts
:
// using destructuring to get the `normalize` method of the client
const { normalize } = new GraphQLNormalizr({ exclude: { users: [ 'preferences' ], blogPosts: [ 'tags' ] } })
normalize(response)
// =>
// {
// users: {
// '5a6efb94b0e8c36f99fba013': {,
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com',
// preferences: null
// },
// '5a6efb94b0e8c36f99fba013': {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Anna.Klaus@gmail.com',
// preferences: { foo: 'apple', bar: 1, baz: { a: 'b' }, quux: null, }
// },
// },
// blogPosts: {
// '5a6cf127c2b20834f6551484': {
// id: '5a6cf127c2b20834f6551484',
// likes: 10,
// title: 'Sunt ut aut',
// tags: {},
// },
// '5a6cf127c2b20834f6551485': {
// id: '5a6cf127c2b20834f6551485',
// likes: 23,
// title: 'Nesciunt esse',
// tags: [],
// },
// }
// }
plural
Boolean
Default is true
. Set this to false
if you don't want to pluralize entity names. Considering the previous response example:
const { normalize } = new GraphQLNormalizr({
plural: false,
})
normalize(response)
// =>
// {
// user: {
// '5a6efb94b0e8c36f99fba013' : {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// }
// }
casing
'lower'|'upper'|'camel'|'pascal'|'snake'|'kebab'
You can also specify the preferred casing for entity names. Again, consider the above response example.
// casing: 'lower'
// User => user
// casing: 'upper'
// User => USER
// casing: 'camel'
// PrimaryAddress => primaryAddress
// casing: 'pascal'
// PrimaryAddress => PrimaryAddress
// casing: 'snake'
// PrimaryAddress => primary_address
// casing: 'kebab'
// PrimaryAddress => primary-address
Combine plural
and casing
options to get the desired entity names
lists
Boolean
Default is false
. All the data is stored in key/value pairs, for easy access. If you want to use arrays, for whatever reason, set this to true
For the same response object in our previous example:
const { normalize } = new GraphQLNormalizr({
lists: true,
})
normalize(response)
// =>
// {
// users: [
// {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// ]
// }
typenames
Boolean
Default is false
. The normalized data will not contain the __typename
field. Set this to true
if you need to persist them.
const { normalize } = new GraphQLNormalizr({
typenames: true,
})
normalize(response)
// =>
// {
// users: {
// '5a6efb94b0e8c36f99fba013' : {
// __typename: 'User',
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// }
// }
typePointers
Boolean
Default is false
. Enables explicit type pointers - instead of an array of only identifiers and having to figure out which collection they point to, it will return objects containing the identifier as well as the collection name. Works especially well with Union types and Interfaces.
const { normalize } = new GraphQLNormalizr({
typePointers: true,
})
// ['5a6cf127c2b20834f655148a', '5a6cf127c2b20834f655148b', '5a6cf127c2b20834f655148c']
users: [
{
_id: '5a6cf127c2b20834f655148a', // '_id' or the specified key
collection: 'members', // type Member
},
{
_id: '5a6cf127c2b20834f655148b', // '_id' or the specified key
collection: 'authors', // type Author
},
{
_id: '5a6cf127c2b20834f655148c', // '_id' or the specified key
collection: 'members', // type Member
},
],
caching
Boolean
Default is false
. The normalize method is pretty fast by itself, it does a single iteration and associates the values only for each response node and not for all the fields. Enable this if you think you'd be normalizing the same response multiple times, like when you're polling for data and it may not have changed.
const { normalize } = new GraphQLNormalizr({
caching: true,
})
const normalized = normalize(response)
const cached = normalize(response)
cached === normalized // => true
parse
Turns a GraphQL source into a Document and adds the required fields where necessary.
// ...
import { GraphQLNormalizr } from 'graphql-normalizr'
const source = `{
allUsers {
email
posts {
title
comments {
message
}
}
}
}`
const { parse } = new GraphQLNormalizr()
const query = parse(source) // will add `id` and `__typename` fields to all the nodes
// We can use the print method from `graphql` to see/use the updated source
const { print } = require('graphql')
print(query)
// =>
// `{
// allUsers {
// __typename
// id
// email
// posts {
// __typename
// id
// comments {
// __typename
// id
// message
// }
// }
// }
// }`
// ...
addRequiredFields
If you only have access to the Document, you can use the print method from graphql
to get the source and parse it. But that may be expensive and you shouldn't have to print a document just to parse it again. addRequiredFields
will add the id
and __typename
fields to that document, without the need of extracting its source.
// ...
import { GraphQLNormalizr } from 'graphql-normalizr'
import { allUsersQuery } from './queries'
const { addRequiredFields } = new GraphQLNormalizr()
const query = addRequiredFields(allUsersQuery)
// ...
normalize
The following is a full example where we use apollo-fetch to execute a query and then normalize it with GraphQLNormalizr
const { GraphQLNormalizr } = require('graphql-normalizr')
const { createApolloFetch } = require('apollo-fetch')
const uri = 'http://localhost:8080/graphql'
const fetch = createApolloFetch({ uri })
const source = `
query {
allUsers {
...userFields
}
}
fragment userFields on User {
email
posts {
title
comments {
message
}
}
}
`
const { normalize, parse } = new GraphQLNormalizr()
const query = parse(source)
fetch({ query }).then(response => {
const normalized = normalize(response)
// persist the normalized data to our app state.
}).catch(...)
from v1.x to v2.x
There aren't many breaking changes between v1.x and v2.x. In fact, there's only one and it's about how Type names get converted into entity names.
With the default configuration, v1.x will transfrom a PrimaryAddress
type name into primaryaddresses
entity name. With 2.x, the default configuiration will transform PrimaryAddress
to primaryAddresses
. The only difference is that now, it changes to camelcase instead of lowercase
If you don't want to change your code everywhere you are accessing entities, you can configure the way GraphQLNormalizr makes the transformation with the plural and casing options:
const { normalize } = new GraphQLNormalizr({
plural: true, // true is the default value, so you can omit this
casing: 'lower',
})
// this above configuration will change `PrimaryAddress` to `primaryaddresses`
Author: monojack
Source Code: https://github.com/monojack/graphql-normalizr
License: MIT License
1651755600
Normalize GraphQL responses for persisting in the client cache/state.
Not related, in any way, to normalizr, just shamelessly piggybacking on its popularity. Also, "normalizEr" is taken...
TL;DR: Transforms:
{
"data": {
"findUser": [
{
"__typename": "User",
"id": "5a6efb94b0e8c36f99fba013",
"email": "Lloyd.Nikolaus@yahoo.com",
"posts": [
{
"__typename": "BlogPost",
"id": "5a6efb94b0e8c36f99fba016",
"title": "Dolorem voluptatem molestiae",
"comments": [
{
"__typename": "Comment",
"id": "5a6efb94b0e8c36f99fba019",
"message": "Alias quod est voluptatibus aut quis sunt aut numquam."
},
{
"__typename": "Comment",
"id": "5a6efb94b0e8c36f99fba01b",
"message": "Harum quia asperiores nemo."
},
{
"__typename": "Comment",
"id": "5a6efb94b0e8c36f99fba01c",
"message": "Vel veniam consectetur laborum."
},
{
"__typename": "Comment",
"id": "5a6efb94b0e8c36f99fba01e",
"message": "Possimus beatae vero recusandae beatae quas ut commodi laboriosam."
}
]
}
]
}
]
}
}
into:
{
"comments": {
"5a6efb94b0e8c36f99fba019": {
"id": "5a6efb94b0e8c36f99fba019",
"message": "Alias quod est voluptatibus aut quis sunt aut numquam."
},
"5a6efb94b0e8c36f99fba01b": {
"id": "5a6efb94b0e8c36f99fba01b",
"message": "Harum quia asperiores nemo."
},
"5a6efb94b0e8c36f99fba01c": {
"id": "5a6efb94b0e8c36f99fba01c",
"message": "Vel veniam consectetur laborum."
},
"5a6efb94b0e8c36f99fba01e": {
"id": "5a6efb94b0e8c36f99fba01e",
"message": "Possimus beatae vero recusandae beatae quas ut commodi laboriosam."
}
},
"blogPosts": {
"5a6efb94b0e8c36f99fba016": {
"id": "5a6efb94b0e8c36f99fba016",
"title": "Dolorem voluptatem molestiae",
"comments": [
"5a6efb94b0e8c36f99fba019",
"5a6efb94b0e8c36f99fba01b",
"5a6efb94b0e8c36f99fba01c",
"5a6efb94b0e8c36f99fba01e"
]
}
},
"users": {
"5a6efb94b0e8c36f99fba013": {
"id": "5a6efb94b0e8c36f99fba013",
"email": "Lloyd.Nikolaus@yahoo.com",
"posts": ["5a6efb94b0e8c36f99fba016"]
}
}
}
We all love GraphQL and we want to use it. There are tons of libraries and clients out there that help us do that with ease, but there is still one problem... How do you persist that data?
Yes, everything is all great when the response mirrors the exact structure we asked for, but we don't want to cache it that way, do we? We probably want a normalized version of that data which we can persist to our store and read/modify it efficiently. Flux or Redux stores work best with normalized data and there are also GraphQL clients you can use to execute queries on the local cache/state (blips or apollo-link-state), in which case, we definitely need to persist normalized data.
GraphQLNormalizr is simple, fast, light-weight and it provides all the tools needed to do just that, the only requirement is that you include the id
and __typename
fields for all the nodes (but it can do that for you if you're too lazy or you want to keep your sources thin).
npm install graphql-normalizr
The GraphQLNormalizr constructor function returns an object containing 3 methods:
Depending on how you write your queries, you may or may not use parse
or addRequiredFields
, but normalize
is the method that you will transform the GraphQL response. As you've probably seen from the TL;DR, all response nodes must contain the __typename
and id
fields. __typename
is a GraphQL meta field and the id
key may be customized when creating the GraphQLNormalizr client.
If your queries already ask for id
and __typename
there's no need to use parse or addRequiredFields. Otherwise, parse will take care of transforming your GraphQL source
into a Document
and add the __typename
and id
fields where needed. In case you already use a different parser, or only have access to the Document
you may use addRequiredFields on the Document
to add the __typename
and id
fields
import { GraphQLNormalizr } from 'graphql-normalizr'
// const config = ...
const normalizer = new GraphQLNormalizr(config)
config: optional - the configuration object containing information for instantiating the client. it takes the following props:
idKey
String
Default is "id". Configures a custom id
key for the client. Use this if your resource identifiers are found under a different key name ('_id', 'key', 'uid' etc).
Consider the following GraphQL response:
const response = {
data: {
findUser: {
__typename: 'User',
uid: '5a6efb94b0e8c36f99fba013',
email: 'Lloyd.Nikolaus@yahoo.com',
},
},
}
Normalize the data with our custom id
key:
// using destructuring to get the `normalize` method of the client
const { normalize } = new GraphQLNormalizr({ idKey: 'uid' })
normalize(response)
// =>
// {
// users: {
// '5a6efb94b0e8c36f99fba013' : {
// uid: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// }
// }
useConnections
Boolean
Default is false
. If you are using GraphQL connections with edges
and nodes
, set this flag to true
otherwise you'll get a warning and the normalization won't work.
NOTE: The connections implementation needs to be according to the specification
const response = {
data: {
findUser: {
__typename: 'User',
id: '5a6efb94b0e8c36f99fba013',
email: 'Lloyd.Nikolaus@yahoo.com',
friends: {
__typename: 'FriendsConnection',
totalCount: 3,
edges: [
{
node: {
__typename: 'User',
id: '5a6cf127c2b20834f6551481',
email: 'Madisen_Braun@hotmail.com',
},
cursor: 'Y3Vyc29yMg==',
},
{
node: {
__typename: 'User',
id: '5a6cf127c2b20834f6551482',
email: 'Robel.Ansel@yahoo.com',
},
cursor: 'Y3Vyc29yMw==',
},
],
pageInfo: {
endCursor: 'Y3Vyc29yMw==',
hasNextPage: false,
},
},
},
},
}
const { normalize } = new GraphQLNormalizr({
useConnections: true,
})
normalize(response)
// =>
// {
// users: {
// '5a6efb94b0e8c36f99fba013': {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com',
// friends: ['5a6cf127c2b20834f6551481', '5a6cf127c2b20834f6551482'],
// },
// '5a6cf127c2b20834f6551481': {
// id: '5a6cf127c2b20834f6551481',
// email: 'Madisen_Braun@hotmail.com',
// },
// '5a6cf127c2b20834f6551482': {
// id: '5a6cf127c2b20834f6551482',
// email: 'Robel.Ansel@yahoo.com',
// },
// },
// }
typeMap
Object
By default the entity name will be the plural form of the type name, converted to camel case, (PrimaryAddress
type will be stored under the primaryAddresses
key). Use this option to provide specific entity names for some/all Types, or try the plural and casing options to derive the entity names.
const response = {
data: {
findUser: {
__typename: 'User',
id: '5a6efb94b0e8c36f99fba013',
email: 'Lloyd.Nikolaus@yahoo.com',
},
},
}
const { normalize } = new GraphQLNormalizr({
typeMap: { User: 'accounts' },
})
normalize(response)
// =>
// {
// accounts: {
// '5a6efb94b0e8c36f99fba013' : {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// }
// }
exclude
Object
Prevent normalization of specified fields
const response = {
data: {
allUsers: [
{
__typename: 'User',
id: '5a6efb94b0e8c36f99fba013',
email: 'Lloyd.Nikolaus@yahoo.com',
preferences: null
posts: [
{
__typename: 'BlogPost',
id: '5a6cf127c2b20834f6551484',
likes: 10,
title: 'Sunt ut aut',
tags: {},
}
]
},
{
__typename: 'User',
id: '5a6efb94b0e8c36f99fba013',
email: 'Anna.Klaus@gmail.com',
preferences: { foo: 'apple', bar: 1, baz: { a: 'b' }, quux: null, }
posts: [
{
__typename: 'BlogPost',
id: '5a6cf127c2b20834f6551485',
likes: 23,
title: 'Nesciunt esse',
tags: [],
}
]
},
],
},
}
Normalize the data excluding the preferences
field on users
and the tags
field on blogPosts
:
// using destructuring to get the `normalize` method of the client
const { normalize } = new GraphQLNormalizr({ exclude: { users: [ 'preferences' ], blogPosts: [ 'tags' ] } })
normalize(response)
// =>
// {
// users: {
// '5a6efb94b0e8c36f99fba013': {,
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com',
// preferences: null
// },
// '5a6efb94b0e8c36f99fba013': {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Anna.Klaus@gmail.com',
// preferences: { foo: 'apple', bar: 1, baz: { a: 'b' }, quux: null, }
// },
// },
// blogPosts: {
// '5a6cf127c2b20834f6551484': {
// id: '5a6cf127c2b20834f6551484',
// likes: 10,
// title: 'Sunt ut aut',
// tags: {},
// },
// '5a6cf127c2b20834f6551485': {
// id: '5a6cf127c2b20834f6551485',
// likes: 23,
// title: 'Nesciunt esse',
// tags: [],
// },
// }
// }
plural
Boolean
Default is true
. Set this to false
if you don't want to pluralize entity names. Considering the previous response example:
const { normalize } = new GraphQLNormalizr({
plural: false,
})
normalize(response)
// =>
// {
// user: {
// '5a6efb94b0e8c36f99fba013' : {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// }
// }
casing
'lower'|'upper'|'camel'|'pascal'|'snake'|'kebab'
You can also specify the preferred casing for entity names. Again, consider the above response example.
// casing: 'lower'
// User => user
// casing: 'upper'
// User => USER
// casing: 'camel'
// PrimaryAddress => primaryAddress
// casing: 'pascal'
// PrimaryAddress => PrimaryAddress
// casing: 'snake'
// PrimaryAddress => primary_address
// casing: 'kebab'
// PrimaryAddress => primary-address
Combine plural
and casing
options to get the desired entity names
lists
Boolean
Default is false
. All the data is stored in key/value pairs, for easy access. If you want to use arrays, for whatever reason, set this to true
For the same response object in our previous example:
const { normalize } = new GraphQLNormalizr({
lists: true,
})
normalize(response)
// =>
// {
// users: [
// {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// ]
// }
typenames
Boolean
Default is false
. The normalized data will not contain the __typename
field. Set this to true
if you need to persist them.
const { normalize } = new GraphQLNormalizr({
typenames: true,
})
normalize(response)
// =>
// {
// users: {
// '5a6efb94b0e8c36f99fba013' : {
// __typename: 'User',
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// }
// }
typePointers
Boolean
Default is false
. Enables explicit type pointers - instead of an array of only identifiers and having to figure out which collection they point to, it will return objects containing the identifier as well as the collection name. Works especially well with Union types and Interfaces.
const { normalize } = new GraphQLNormalizr({
typePointers: true,
})
// ['5a6cf127c2b20834f655148a', '5a6cf127c2b20834f655148b', '5a6cf127c2b20834f655148c']
users: [
{
_id: '5a6cf127c2b20834f655148a', // '_id' or the specified key
collection: 'members', // type Member
},
{
_id: '5a6cf127c2b20834f655148b', // '_id' or the specified key
collection: 'authors', // type Author
},
{
_id: '5a6cf127c2b20834f655148c', // '_id' or the specified key
collection: 'members', // type Member
},
],
caching
Boolean
Default is false
. The normalize method is pretty fast by itself, it does a single iteration and associates the values only for each response node and not for all the fields. Enable this if you think you'd be normalizing the same response multiple times, like when you're polling for data and it may not have changed.
const { normalize } = new GraphQLNormalizr({
caching: true,
})
const normalized = normalize(response)
const cached = normalize(response)
cached === normalized // => true
parse
Turns a GraphQL source into a Document and adds the required fields where necessary.
// ...
import { GraphQLNormalizr } from 'graphql-normalizr'
const source = `{
allUsers {
email
posts {
title
comments {
message
}
}
}
}`
const { parse } = new GraphQLNormalizr()
const query = parse(source) // will add `id` and `__typename` fields to all the nodes
// We can use the print method from `graphql` to see/use the updated source
const { print } = require('graphql')
print(query)
// =>
// `{
// allUsers {
// __typename
// id
// email
// posts {
// __typename
// id
// comments {
// __typename
// id
// message
// }
// }
// }
// }`
// ...
addRequiredFields
If you only have access to the Document, you can use the print method from graphql
to get the source and parse it. But that may be expensive and you shouldn't have to print a document just to parse it again. addRequiredFields
will add the id
and __typename
fields to that document, without the need of extracting its source.
// ...
import { GraphQLNormalizr } from 'graphql-normalizr'
import { allUsersQuery } from './queries'
const { addRequiredFields } = new GraphQLNormalizr()
const query = addRequiredFields(allUsersQuery)
// ...
normalize
The following is a full example where we use apollo-fetch to execute a query and then normalize it with GraphQLNormalizr
const { GraphQLNormalizr } = require('graphql-normalizr')
const { createApolloFetch } = require('apollo-fetch')
const uri = 'http://localhost:8080/graphql'
const fetch = createApolloFetch({ uri })
const source = `
query {
allUsers {
...userFields
}
}
fragment userFields on User {
email
posts {
title
comments {
message
}
}
}
`
const { normalize, parse } = new GraphQLNormalizr()
const query = parse(source)
fetch({ query }).then(response => {
const normalized = normalize(response)
// persist the normalized data to our app state.
}).catch(...)
from v1.x to v2.x
There aren't many breaking changes between v1.x and v2.x. In fact, there's only one and it's about how Type names get converted into entity names.
With the default configuration, v1.x will transfrom a PrimaryAddress
type name into primaryaddresses
entity name. With 2.x, the default configuiration will transform PrimaryAddress
to primaryAddresses
. The only difference is that now, it changes to camelcase instead of lowercase
If you don't want to change your code everywhere you are accessing entities, you can configure the way GraphQLNormalizr makes the transformation with the plural and casing options:
const { normalize } = new GraphQLNormalizr({
plural: true, // true is the default value, so you can omit this
casing: 'lower',
})
// this above configuration will change `PrimaryAddress` to `primaryaddresses`
Author: monojack
Source Code: https://github.com/monojack/graphql-normalizr
License: MIT License
1648666800
Normalize GraphQL responses for persisting in the client cache/state.
Not related, in any way, to normalizr, just shamelessly piggybacking on its popularity. Also, "normalizEr" is taken...
TL;DR: Transforms:
{
"data": {
"findUser": [
{
"__typename": "User",
"id": "5a6efb94b0e8c36f99fba013",
"email": "Lloyd.Nikolaus@yahoo.com",
"posts": [
{
"__typename": "BlogPost",
"id": "5a6efb94b0e8c36f99fba016",
"title": "Dolorem voluptatem molestiae",
"comments": [
{
"__typename": "Comment",
"id": "5a6efb94b0e8c36f99fba019",
"message": "Alias quod est voluptatibus aut quis sunt aut numquam."
},
{
"__typename": "Comment",
"id": "5a6efb94b0e8c36f99fba01b",
"message": "Harum quia asperiores nemo."
},
{
"__typename": "Comment",
"id": "5a6efb94b0e8c36f99fba01c",
"message": "Vel veniam consectetur laborum."
},
{
"__typename": "Comment",
"id": "5a6efb94b0e8c36f99fba01e",
"message": "Possimus beatae vero recusandae beatae quas ut commodi laboriosam."
}
]
}
]
}
]
}
}
into:
{
"comments": {
"5a6efb94b0e8c36f99fba019": {
"id": "5a6efb94b0e8c36f99fba019",
"message": "Alias quod est voluptatibus aut quis sunt aut numquam."
},
"5a6efb94b0e8c36f99fba01b": {
"id": "5a6efb94b0e8c36f99fba01b",
"message": "Harum quia asperiores nemo."
},
"5a6efb94b0e8c36f99fba01c": {
"id": "5a6efb94b0e8c36f99fba01c",
"message": "Vel veniam consectetur laborum."
},
"5a6efb94b0e8c36f99fba01e": {
"id": "5a6efb94b0e8c36f99fba01e",
"message": "Possimus beatae vero recusandae beatae quas ut commodi laboriosam."
}
},
"blogPosts": {
"5a6efb94b0e8c36f99fba016": {
"id": "5a6efb94b0e8c36f99fba016",
"title": "Dolorem voluptatem molestiae",
"comments": [
"5a6efb94b0e8c36f99fba019",
"5a6efb94b0e8c36f99fba01b",
"5a6efb94b0e8c36f99fba01c",
"5a6efb94b0e8c36f99fba01e"
]
}
},
"users": {
"5a6efb94b0e8c36f99fba013": {
"id": "5a6efb94b0e8c36f99fba013",
"email": "Lloyd.Nikolaus@yahoo.com",
"posts": ["5a6efb94b0e8c36f99fba016"]
}
}
}
We all love GraphQL and we want to use it. There are tons of libraries and clients out there that help us do that with ease, but there is still one problem... How do you persist that data?
Yes, everything is all great when the response mirrors the exact structure we asked for, but we don't want to cache it that way, do we? We probably want a normalized version of that data which we can persist to our store and read/modify it efficiently. Flux or Redux stores work best with normalized data and there are also GraphQL clients you can use to execute queries on the local cache/state (blips or apollo-link-state), in which case, we definitely need to persist normalized data.
GraphQLNormalizr is simple, fast, light-weight and it provides all the tools needed to do just that, the only requirement is that you include the id
and __typename
fields for all the nodes (but it can do that for you if you're too lazy or you want to keep your sources thin).
npm install graphql-normalizr
The GraphQLNormalizr constructor function returns an object containing 3 methods:
Depending on how you write your queries, you may or may not use parse
or addRequiredFields
, but normalize
is the method that you will transform the GraphQL response. As you've probably seen from the TL;DR, all response nodes must contain the __typename
and id
fields. __typename
is a GraphQL meta field and the id
key may be customized when creating the GraphQLNormalizr client.
If your queries already ask for id
and __typename
there's no need to use parse or addRequiredFields. Otherwise, parse will take care of transforming your GraphQL source
into a Document
and add the __typename
and id
fields where needed. In case you already use a different parser, or only have access to the Document
you may use addRequiredFields on the Document
to add the __typename
and id
fields
import { GraphQLNormalizr } from 'graphql-normalizr'
// const config = ...
const normalizer = new GraphQLNormalizr(config)
config: optional - the configuration object containing information for instantiating the client. it takes the following props:
idKey
String
Default is "id". Configures a custom id
key for the client. Use this if your resource identifiers are found under a different key name ('_id', 'key', 'uid' etc).
Consider the following GraphQL response:
const response = {
data: {
findUser: {
__typename: 'User',
uid: '5a6efb94b0e8c36f99fba013',
email: 'Lloyd.Nikolaus@yahoo.com',
},
},
}
Normalize the data with our custom id
key:
// using destructuring to get the `normalize` method of the client
const { normalize } = new GraphQLNormalizr({ idKey: 'uid' })
normalize(response)
// =>
// {
// users: {
// '5a6efb94b0e8c36f99fba013' : {
// uid: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// }
// }
useConnections
Boolean
Default is false
. If you are using GraphQL connections with edges
and nodes
, set this flag to true
otherwise you'll get a warning and the normalization won't work.
NOTE: The connections implementation needs to be according to the specification
const response = {
data: {
findUser: {
__typename: 'User',
id: '5a6efb94b0e8c36f99fba013',
email: 'Lloyd.Nikolaus@yahoo.com',
friends: {
__typename: 'FriendsConnection',
totalCount: 3,
edges: [
{
node: {
__typename: 'User',
id: '5a6cf127c2b20834f6551481',
email: 'Madisen_Braun@hotmail.com',
},
cursor: 'Y3Vyc29yMg==',
},
{
node: {
__typename: 'User',
id: '5a6cf127c2b20834f6551482',
email: 'Robel.Ansel@yahoo.com',
},
cursor: 'Y3Vyc29yMw==',
},
],
pageInfo: {
endCursor: 'Y3Vyc29yMw==',
hasNextPage: false,
},
},
},
},
}
const { normalize } = new GraphQLNormalizr({
useConnections: true,
})
normalize(response)
// =>
// {
// users: {
// '5a6efb94b0e8c36f99fba013': {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com',
// friends: ['5a6cf127c2b20834f6551481', '5a6cf127c2b20834f6551482'],
// },
// '5a6cf127c2b20834f6551481': {
// id: '5a6cf127c2b20834f6551481',
// email: 'Madisen_Braun@hotmail.com',
// },
// '5a6cf127c2b20834f6551482': {
// id: '5a6cf127c2b20834f6551482',
// email: 'Robel.Ansel@yahoo.com',
// },
// },
// }
typeMap
Object
By default the entity name will be the plural form of the type name, converted to camel case, (PrimaryAddress
type will be stored under the primaryAddresses
key). Use this option to provide specific entity names for some/all Types, or try the plural and casing options to derive the entity names.
const response = {
data: {
findUser: {
__typename: 'User',
id: '5a6efb94b0e8c36f99fba013',
email: 'Lloyd.Nikolaus@yahoo.com',
},
},
}
const { normalize } = new GraphQLNormalizr({
typeMap: { User: 'accounts' },
})
normalize(response)
// =>
// {
// accounts: {
// '5a6efb94b0e8c36f99fba013' : {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// }
// }
exclude
Object
Prevent normalization of specified fields
const response = {
data: {
allUsers: [
{
__typename: 'User',
id: '5a6efb94b0e8c36f99fba013',
email: 'Lloyd.Nikolaus@yahoo.com',
preferences: null
posts: [
{
__typename: 'BlogPost',
id: '5a6cf127c2b20834f6551484',
likes: 10,
title: 'Sunt ut aut',
tags: {},
}
]
},
{
__typename: 'User',
id: '5a6efb94b0e8c36f99fba013',
email: 'Anna.Klaus@gmail.com',
preferences: { foo: 'apple', bar: 1, baz: { a: 'b' }, quux: null, }
posts: [
{
__typename: 'BlogPost',
id: '5a6cf127c2b20834f6551485',
likes: 23,
title: 'Nesciunt esse',
tags: [],
}
]
},
],
},
}
Normalize the data excluding the preferences
field on users
and the tags
field on blogPosts
:
// using destructuring to get the `normalize` method of the client
const { normalize } = new GraphQLNormalizr({ exclude: { users: [ 'preferences' ], blogPosts: [ 'tags' ] } })
normalize(response)
// =>
// {
// users: {
// '5a6efb94b0e8c36f99fba013': {,
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com',
// preferences: null
// },
// '5a6efb94b0e8c36f99fba013': {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Anna.Klaus@gmail.com',
// preferences: { foo: 'apple', bar: 1, baz: { a: 'b' }, quux: null, }
// },
// },
// blogPosts: {
// '5a6cf127c2b20834f6551484': {
// id: '5a6cf127c2b20834f6551484',
// likes: 10,
// title: 'Sunt ut aut',
// tags: {},
// },
// '5a6cf127c2b20834f6551485': {
// id: '5a6cf127c2b20834f6551485',
// likes: 23,
// title: 'Nesciunt esse',
// tags: [],
// },
// }
// }
plural
Boolean
Default is true
. Set this to false
if you don't want to pluralize entity names. Considering the previous response example:
const { normalize } = new GraphQLNormalizr({
plural: false,
})
normalize(response)
// =>
// {
// user: {
// '5a6efb94b0e8c36f99fba013' : {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// }
// }
casing
'lower'|'upper'|'camel'|'pascal'|'snake'|'kebab'
You can also specify the preferred casing for entity names. Again, consider the above response example.
// casing: 'lower'
// User => user
// casing: 'upper'
// User => USER
// casing: 'camel'
// PrimaryAddress => primaryAddress
// casing: 'pascal'
// PrimaryAddress => PrimaryAddress
// casing: 'snake'
// PrimaryAddress => primary_address
// casing: 'kebab'
// PrimaryAddress => primary-address
Combine plural
and casing
options to get the desired entity names
lists
Boolean
Default is false
. All the data is stored in key/value pairs, for easy access. If you want to use arrays, for whatever reason, set this to true
For the same response object in our previous example:
const { normalize } = new GraphQLNormalizr({
lists: true,
})
normalize(response)
// =>
// {
// users: [
// {
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// ]
// }
typenames
Boolean
Default is false
. The normalized data will not contain the __typename
field. Set this to true
if you need to persist them.
const { normalize } = new GraphQLNormalizr({
typenames: true,
})
normalize(response)
// =>
// {
// users: {
// '5a6efb94b0e8c36f99fba013' : {
// __typename: 'User',
// id: '5a6efb94b0e8c36f99fba013',
// email: 'Lloyd.Nikolaus@yahoo.com'
// }
// }
// }
typePointers
Boolean
Default is false
. Enables explicit type pointers - instead of an array of only identifiers and having to figure out which collection they point to, it will return objects containing the identifier as well as the collection name. Works especially well with Union types and Interfaces.
const { normalize } = new GraphQLNormalizr({
typePointers: true,
})
// ['5a6cf127c2b20834f655148a', '5a6cf127c2b20834f655148b', '5a6cf127c2b20834f655148c']
users: [
{
_id: '5a6cf127c2b20834f655148a', // '_id' or the specified key
collection: 'members', // type Member
},
{
_id: '5a6cf127c2b20834f655148b', // '_id' or the specified key
collection: 'authors', // type Author
},
{
_id: '5a6cf127c2b20834f655148c', // '_id' or the specified key
collection: 'members', // type Member
},
],
caching
Boolean
Default is false
. The normalize method is pretty fast by itself, it does a single iteration and associates the values only for each response node and not for all the fields. Enable this if you think you'd be normalizing the same response multiple times, like when you're polling for data and it may not have changed.
const { normalize } = new GraphQLNormalizr({
caching: true,
})
const normalized = normalize(response)
const cached = normalize(response)
cached === normalized // => true
parse
Turns a GraphQL source into a Document and adds the required fields where necessary.
// ...
import { GraphQLNormalizr } from 'graphql-normalizr'
const source = `{
allUsers {
email
posts {
title
comments {
message
}
}
}
}`
const { parse } = new GraphQLNormalizr()
const query = parse(source) // will add `id` and `__typename` fields to all the nodes
// We can use the print method from `graphql` to see/use the updated source
const { print } = require('graphql')
print(query)
// =>
// `{
// allUsers {
// __typename
// id
// email
// posts {
// __typename
// id
// comments {
// __typename
// id
// message
// }
// }
// }
// }`
// ...
addRequiredFields
If you only have access to the Document, you can use the print method from graphql
to get the source and parse it. But that may be expensive and you shouldn't have to print a document just to parse it again. addRequiredFields
will add the id
and __typename
fields to that document, without the need of extracting its source.
// ...
import { GraphQLNormalizr } from 'graphql-normalizr'
import { allUsersQuery } from './queries'
const { addRequiredFields } = new GraphQLNormalizr()
const query = addRequiredFields(allUsersQuery)
// ...
normalize
The following is a full example where we use apollo-fetch to execute a query and then normalize it with GraphQLNormalizr
const { GraphQLNormalizr } = require('graphql-normalizr')
const { createApolloFetch } = require('apollo-fetch')
const uri = 'http://localhost:8080/graphql'
const fetch = createApolloFetch({ uri })
const source = `
query {
allUsers {
...userFields
}
}
fragment userFields on User {
email
posts {
title
comments {
message
}
}
}
`
const { normalize, parse } = new GraphQLNormalizr()
const query = parse(source)
fetch({ query }).then(response => {
const normalized = normalize(response)
// persist the normalized data to our app state.
}).catch(...)
from v1.x to v2.x
There aren't many breaking changes between v1.x and v2.x. In fact, there's only one and it's about how Type names get converted into entity names.
With the default configuration, v1.x will transfrom a PrimaryAddress
type name into primaryaddresses
entity name. With 2.x, the default configuiration will transform PrimaryAddress
to primaryAddresses
. The only difference is that now, it changes to camelcase instead of lowercase
If you don't want to change your code everywhere you are accessing entities, you can configure the way GraphQLNormalizr makes the transformation with the plural and casing options:
const { normalize } = new GraphQLNormalizr({
plural: true, // true is the default value, so you can omit this
casing: 'lower',
})
// this above configuration will change `PrimaryAddress` to `primaryaddresses`
Author: monojack
Source Code: https://github.com/monojack/graphql-normalizr
License: MIT License
1623297402
Looking at the previous architecture above, you might notice that there’s a one-to-one ratio of requests that come into the gateway and hit the underlying backend service.
If we wanted to reduce strain on the backend service, we could introduce a cache, preventing the need to fetch data that has already been fetched by the implementing service.
#graphql #graphql architecture #caching #caching strategies
1651813200
Why use this package over the other available Elm GraphQL packages? This is the only one that generates type-safe code for your entire schema. Check out this blog post, Type-Safe & Composable GraphQL in Elm, to learn more about the motivation for this library. (It's also the only type-safe library with Elm 0.18 or 0.19 support, see this discourse thread).
I built this package because I wanted to have something that:
See an example in action on Ellie. See more end-to-end example code in the examples/
folder.
dillonkearns/elm-graphql
is an Elm package and accompanying command-line code generator that creates type-safe Elm code for your GraphQL endpoint. You don't write any decoders for your API with dillonkearns/elm-graphql
, instead you simply select which fields you would like, similar to a standard GraphQL query but in Elm. For example, this GraphQL query
query {
human(id: "1001") {
name
homePlanet
}
}
would look like this in dillonkearns/elm-graphql
(the code in this example that is prefixed with StarWars
is auto-generated)
import Graphql.Operation exposing (RootQuery)
import Graphql.SelectionSet as SelectionSet exposing (SelectionSet)
import StarWars.Object
import StarWars.Object.Human as Human
import StarWars.Query as Query
import StarWars.Scalar exposing (Id(..))
query : SelectionSet (Maybe HumanData) RootQuery
query =
Query.human { id = Id "1001" } humanSelection
type alias HumanData =
{ name : String
, homePlanet : Maybe String
}
humanSelection : SelectionSet HumanData StarWars.Object.Human
humanSelection =
SelectionSet.map2 HumanData
Human.name
Human.homePlanet
GraphQL and Elm are a perfect match because GraphQL is used to enforce the types that your API takes as inputs and outputs, much like Elm's type system does within Elm. elm-graphql
simply bridges this gap by making your Elm code aware of your GraphQL server's schema. If you are new to GraphQL, graphql.org/learn/ is an excellent way to learn the basics.
After following the installation instructions to install the @dillonkearns/elm-graphql
NPM package and the proper Elm packages (see the Setup section for details). Once you've installed everything, running the elm-graphql
code generation tool is as simple as this:
npx elm-graphql https://elm-graphql.herokuapp.com --base StarWars --output examples/src
If headers are required, such as a Bearer Token, the --header
flag can be supplied.
npx elm-graphql https://elm-graphql.herokuapp.com --base StarWars --output examples/src --header 'headerKey: header value'
There is a thorough tutorial in the SelectionSet
docs. SelectionSet
s are the core concept in this library, so I recommend reading through the whole page (it's not very long!).
The examples/
folder is another great place to start.
If you want to learn more GraphQL basics, this is a great tutorial, and a short read: graphql.org/learn/
My Elm Conf 2018 talk goes into the philosophy behind dillonkearns/elm-graphql
(Skip to 13:06 to go straight to the dillonkearns/elm-graphql
demo).
elm-graphql
using the Scalar Codecs feature. If you're wondering why code is generated a certain way, you're likely to find an answer in the Frequently Asked Questions (FAQ).
There's a very helpful group of people in the #graphql channel in the Elm Slack. Don't hesitate to ask any questions about getting started, best practices, or just general GraphQL in there!
dillonkearns/elm-graphql
generates Elm code that allows you to build up type-safe GraphQL requests. Here are the steps to setup dillonkearns/elm-graphql
.
Add the dillonkearns/elm-graphql
elm package as a dependency in your elm.json
. You will also need to make sure that elm/json
is a dependency of your project since the generated code has lots of JSON decoders in it.
elm install dillonkearns/elm-graphql
elm install elm/json
Install the @dillonkearns/elm-graphql
command line tool through npm. This is what you will use to generate Elm code for your API. It is recommended that you save the @dillonkearns/elm-graphql
command line tool as a dev dependency so that everyone on your project is using the same version.
npm install --save-dev @dillonkearns/elm-graphql
# you can now run it locally using `npx elm-graphql`,
# or by calling it through an npm script as in this project's package.json
Run the @dillonkearns/elm-graphql
command line tool installed above to generate your code. If you used the --save-dev
method above, you can simply create a script in your package.json like the following:
{
"name": "star-wars-elm-graphql-project",
"version": "1.0.0",
"scripts": {
"api": "elm-graphql https://elm-graphql.herokuapp.com/api --base StarWars"
}
With the above in your package.json
, running npm run api
will generate dillonkearns/elm-graphql
code for you to call in ./src/StarWars/
. You can now use the generated code as in this Ellie example or in the examples
folder.
You can do real-time APIs using GraphQL Subscriptions and dillonkearns/elm-graphql
. Just wire in the framework-specific JavaScript code for opening the WebSocket connection through a port. Here's a live demo and its source code. The demo server is running Elixir/Absinthe.
Thank you Mario Martinez (martimatix) for all your feedback, the elm-format PR, and for the incredible logo design!
Thank you Mike Stock (mikeastock) for setting up Travis CI!
Thanks for the reserved words pull request @madsflensted!
A huge thanks to @xtian for doing the vast majority of the 0.19 upgrade work! :tada:
Thank you Josh Adams (@knewter) for the code example for Subscriptions with Elixir/Absinthe wired up through Elm ports!
Thank you Romario for adding OptionalArgument.map
!
Thank you Aaron White for your pull request to improve the performance and stability of the elm-format
step! 🎉
All core features are supported. That is, you can build any query or mutation with your dillonkearns/elm-graphql
-generated code, and it is guaranteed to be valid according to your server's schema.
dillonkearns/elm-graphql
will generate code for you to generate subscriptions and decode the responses, but it doesn't deal with the low-level details for how to send them over web sockets. To do that, you will need to use custom code or a package that knows how to communicate over websockets (or whichever protocol) to setup a subscription with your particular framework. See this discussion for why those details are not handled by this library directly.
I would love to hear feedback if you are using GraphQL Subscriptions. In particular, I'd love to see live code examples to drive any improvements to the Subscriptions design. Please ping me on Slack, drop a message in the #graphql channel, or open up a Github issue to discuss!
I would like to investigate generating helpers to make pagination simpler for Connections (based on the Relay Cursor Connections Specification). If you have ideas on this chime in on this thread.
See the full roadmap on Trello.
Author: dillonkearns
Source Code: https://github.com/dillonkearns/elm-graphql
License: View license
1621069824
SwiftGraphQL is a Swift code generator. It lets you create queries using Swift, and guarantees that every query you create is valid.
The library is centered around three core principles:
#graphql #graphql client #swiftgraphql