- An alternative approach to RESTful APIs
- Clients issue queries/mutations to read and update data
- Clients can fetch only the entity fields that are required
- GraphQL query syntax can express complex entity relations => nested objects
- Mitigates the explosion of RESTful endpoints in scenarios where many different representations of an entity are needed
- Graphiql is a query execution UI, also provides good documentation
const express = require('express');
const expressGraphQL = require('express-graphql');
const schema = require('./schema/schema');
// create express app
const app = express();
app.use('/graphql', expressGraphQL({
schema,
graphiql: true
}));
// register app to listen on a port
app.listen(4000, () => {
console.log('Server started!');
});
- GraphQL Schema types define the data contract between the client and the server
const GraphQL = require('graphql');
const {
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLList,
GraphQLSchema,
GraphQLNonNull
} = GraphQL;
// define the company schema type
const CompanyType = new GraphQLObjectType({
name: 'Company',
// use a closure to work around cyclical references
fields: () => ({
id: { type: GraphQLString },
name: { type: GraphQLString },
description: { type: GraphQLString },
users: {
type: new GraphQLList(UserType),
resolve(parentValue, args){
return axios.get(`http://localhost:3000/companies/${parentValue.id}/users`)
.then(resp => resp.data);
}
}
})
});
// define the user schema type
const UserType = new GraphQLObjectType({
name: 'User',
// use a closure to work around cyclical references
fields: () => ({
id: { type: GraphQLString },
firstName: { type: GraphQLString },
age: { type: GraphQLInt },
company: {
type: CompanyType,
resolve(parentValue, args) {
return axios.get(`http://localhost:3000/companies/${parentValue.companyId}`)
.then(resp => resp.data);
}
}
})
});
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
user: {
type: UserType,
args: { id: { type: GraphQLString }},
resolve(parentValue, args) {
return [];
}
},
company: {
type: CompanyType,
args: { id: { type: GraphQLString }},
resolve(parentValue, args) {
// async promise
return axios.get(`http://localhost:3000/companies/${args.id}`)
.then(resp => resp.data);
}
}
}
});
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
addUser: {
type: UserType,
args: {
firstName: { type: new GraphQLNonNull(GraphQLString) },
age: { type: new GraphQLNonNull(GraphQLInt) },
companyId: { type: GraphQLString }
},
resolve(parentValue, { firstName, age }) {
return axios.post(`http://localhost:3000/users`, {
firstName,
age
}).then(res => res.data);
}
},
editUser: {
type: UserType,
args: {
id: { type: new GraphQLNonNull(GraphQLString) },
firstName: { type: GraphQLString },
age: { type: GraphQLInt },
companyId: { type: GraphQLString }
},
resolve(parentValue, { id, firstName, age, companyId }) {
var updatedUser = { firstName, age, companyId };
return axios.patch(`http://localhost:3000/users/${id}`, updatedUser)
.then(res => res.data);
}
}
}
});
Simple Query:
query {
user(id: "40"){
id
firstName
age
}
}
Query With Relations:
query {
user(id: "40"){
id
firstName
age
company {
id,
name,
description
}
}
}
Query Fragments:
query findCompanyUsers {
apple: company(id:"1"){
...companyDetails
}
google: company(id:"2"){
...companyDetails
}
}
fragment companyDetails on Company {
id
name
description
users {
id
firstName
age
}
}
Parameterized Queries:
query UserQuery($id: ID!){
song(id: $id){
id
firstName
email
}
}
Parameter Values:
{
"id": 12345
}
Mutation:
mutation {
addUser(firstName: "Jeff", age: 34){
id
firstName
age
}
}
- Lokka - Simple implementation: basic queries, mutations, and simple caching
- Apollo Client - Good balance between features and complexity
- Relay - Amazing performance on mobile, but the most complex.
Configuring a basic GraphQL client:
// ...
import ApolloClient from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
// tracks objects by the ID field
const client = new ApolloClient({
dataIdFromObject: object => object.id
});
const Root = () => {
return (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
};
ReactDOM.render(
<Root />,
document.querySelector('#root')
);
Configuring the GraphQL client network interface:
const networkInterface = createNetworkInterface({
uri: '/graphql',
opts: {
credentials: 'same-origin' // same-origin request, send cookies w/ request
}
});
const client = new ApolloClient({
networkInterface,
dataIdFromObject: o => o.id
});
Defining a simple GraphQL query:
import gql from 'graphql-tag';
export default gql`
query {
currentUser {
id
email
}
}
`;
Defining a parameterized GraphQL query:
import gql from 'graphql-tag';
export default gql`
query UserByIdQuery($id: ID!) {
user(id: $id){
id
email
}
}
`;
Defining a parameterized GraphQL mutation:
import gql from 'graphql-tag';
export default gql`
mutation LoginMutation($email: String, $password: String) {
login(email: $email, password: $password) {
id
email
}
}
`;
import React, { Component } from 'react';
import gql from 'graphql-tag';
class HelloGraphQL extends Component {
render(){
return (
<p>
Hello, {this.props.data.name}
</p>
);
}
}
const currentUser = gql`
query {
currentUser {
id
firstName
email
}
}
`;
// funky syntax, but this binds the query to the component props
export default graphql(userQuery)(HelloGraphQL);
An alternative syntax:
// ...
// this reads a little better
const withData = graphql(userQuery);
export default withData(HelloGraphQL);
An example of a basic mutation:
import React, { Component } from 'react';
import gql from 'graphql-tag';
import ListUsers from '../queries/ListUsers';
class MutationExample extends Component {
onDeleteUser(){
const id = 123;
this.props.mutate({ variables: { id } })
.then(() => this.props.data.refetch());
};
}
render(){
return (
<div>
<button onClick={this.onDeleteUser.bind(this)}>DeleteUser</button
</div>
);
}
}
const deleteUserMutation = gql`
mutation DeleteUser($id: ID){
deleteUser(id: $id){
id
}
}
`;
const withMutation = graphql(deleteUserMutation);
export withMutation(MutationExample);
Explicitly refetching GraphQL queries after a mutation:
// explicity reference the ListUsersQuery query for refetch
this.props.mutate({
variables: { id },
refetchQueries: [ { ListUsersQuery } ]
});
const withData = graphql(listUsersQuery);
const withMutation = graphql(deleteUserMutation);
export withData(withMutation(UsersList));
import { compose } from 'react-apollo';
const ComponentWithMutations = compose(
graphql(submitNewUser, { name: 'newUserMutation' }),
graphql(submitRepository, { name: 'newRepositoryMutation' })
)(Component);
@claudiucs123 Sorry, this was for my own reference. I guess I should've made this gist private.