Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Express.js and GraphQL

GraphQL is a query language for your API, providing a more efficient and flexible alternative to REST. This guide covers key concepts, examples, and best practices for integrating GraphQL with Express.js applications.

Key Concepts of GraphQL

  • Schema: Defines the structure of the data that clients can query.
  • Queries: Requests to read data from the API.
  • Mutations: Requests to modify data on the API.
  • Resolvers: Functions that handle the logic for fetching the data specified in the queries and mutations.
  • Types: Define the shape of the data and the relationships between different pieces of data.

Setting Up GraphQL with Express.js

Use the express-graphql and graphql packages to set up GraphQL with Express.js:

Example: Basic GraphQL Setup

// Install necessary packages
// npm install express express-graphql graphql

// server.js
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');

const app = express();
const port = 3000;

// Define GraphQL schema
const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

// Define resolvers
const root = {
  hello: () => 'Hello, World!'
};

// Set up GraphQL endpoint
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}/graphql`);
});

Defining GraphQL Schemas and Resolvers

Define more complex schemas and resolvers to handle different types of data:

Example: Defining Schemas and Resolvers

// server.js (additional code)

// Define GraphQL schema
const schema = buildSchema(`
  type Query {
    user(id: ID!): User
    users: [User]
  }

  type Mutation {
    createUser(name: String!, age: Int!): User
  }

  type User {
    id: ID!
    name: String!
    age: Int!
  }
`);

let users = [
  { id: 1, name: 'John Doe', age: 28 },
  { id: 2, name: 'Jane Smith', age: 34 }
];

// Define resolvers
const root = {
  user: ({ id }) => users.find(user => user.id == id),
  users: () => users,
  createUser: ({ name, age }) => {
    const user = { id: users.length + 1, name, age };
    users.push(user);
    return user;
  }
};

// Set up GraphQL endpoint
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}/graphql`);
});

Handling GraphQL Mutations

Handle mutations to modify data on the server:

Example: Handling Mutations

// server.js (additional code)

// Define GraphQL schema with mutations
const schema = buildSchema(`
  type Query {
    user(id: ID!): User
    users: [User]
  }

  type Mutation {
    createUser(name: String!, age: Int!): User
    updateUser(id: ID!, name: String, age: Int): User
    deleteUser(id: ID!): User
  }

  type User {
    id: ID!
    name: String!
    age: Int!
  }
`);

let users = [
  { id: 1, name: 'John Doe', age: 28 },
  { id: 2, name: 'Jane Smith', age: 34 }
];

// Define resolvers with mutations
const root = {
  user: ({ id }) => users.find(user => user.id == id),
  users: () => users,
  createUser: ({ name, age }) => {
    const user = { id: users.length + 1, name, age };
    users.push(user);
    return user;
  },
  updateUser: ({ id, name, age }) => {
    const user = users.find(user => user.id == id);
    if (!user) return null;
    if (name) user.name = name;
    if (age) user.age = age;
    return user;
  },
  deleteUser: ({ id }) => {
    const index = users.findIndex(user => user.id == id);
    if (index === -1) return null;
    const deletedUser = users.splice(index, 1);
    return deletedUser[0];
  }
};

// Set up GraphQL endpoint
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}/graphql`);
});

Integrating with a Database

Integrate your GraphQL API with a database for persistent data storage:

Example: Integrating with MongoDB

// Install necessary packages
// npm install mongoose

// server.js (additional code)
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/express-graphql', { useNewUrlParser: true, useUnifiedTopology: true });

const UserSchema = new mongoose.Schema({
  name: String,
  age: Number
});

const User = mongoose.model('User', UserSchema);

// Define resolvers with database integration
const root = {
  user: async ({ id }) => await User.findById(id),
  users: async () => await User.find({}),
  createUser: async ({ name, age }) => {
    const user = new User({ name, age });
    return await user.save();
  },
  updateUser: async ({ id, name, age }) => {
    return await User.findByIdAndUpdate(id, { name, age }, { new: true });
  },
  deleteUser: async ({ id }) => {
    return await User.findByIdAndRemove(id);
  }
};

// Set up GraphQL endpoint
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true
}));

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}/graphql`);
});

Using Apollo Server with Express.js

Use Apollo Server to create a more robust GraphQL server with additional features:

Example: Setting Up Apollo Server

// Install necessary packages
// npm install apollo-server-express express graphql

// server.js
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');

const app = express();
const port = 3000;

// Define GraphQL schema
const typeDefs = gql`
  type Query {
    hello: String
  }

  type User {
    id: ID!
    name: String!
    age: Int!
  }

  type Mutation {
    createUser(name: String!, age: Int!): User
  }
`;

// Define resolvers
const resolvers = {
  Query: {
    hello: () => 'Hello, World!'
  },
  Mutation: {
    createUser: (_, { name, age }) => {
      const user = { id: users.length + 1, name, age };
      users.push(user);
      return user;
    }
  }
};

const server = new ApolloServer({ typeDefs, resolvers });
server.applyMiddleware({ app });

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}${server.graphqlPath}`);
});

Best Practices for GraphQL

  • Define a Clear Schema: Define a clear and well-structured schema to make your API intuitive and easy to use.
  • Implement Efficient Resolvers: Implement efficient resolvers to handle data fetching and manipulation efficiently.
  • Use Pagination: Implement pagination to handle large datasets efficiently.
  • Implement Error Handling: Implement proper error handling in your resolvers to return meaningful error messages.
  • Secure Your API: Implement authentication and authorization to secure your API and protect sensitive data.

Testing GraphQL APIs

Test your GraphQL APIs to ensure they work as expected:

Example: Testing with Mocha

// Install Mocha and Chai
// npm install --save-dev mocha chai

// test/graphql.test.js
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');
const express = require('express');

const app = express();

// Define GraphQL schema
const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

// Define resolvers
const root = {
  hello: () => 'Hello, World!'
};

// Set up GraphQL endpoint
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: false
}));

describe('GraphQL API', () => {
  it('should return Hello, World!', (done) => {
    request(app)
      .post('/graphql')
      .send({ query: '{ hello }' })
      .expect(200)
      .end((err, res) => {
        if (err) return done(err);
        expect(res.body.data.hello).to.equal('Hello, World!');
        done();
      });
  });
});

// Define test script in package.json
// "scripts": {
//   "test": "mocha"
// }

// Run tests with NPM
// npm run test

Key Points

  • Schema: Defines the structure of the data that clients can query.
  • Queries: Requests to read data from the API.
  • Mutations: Requests to modify data on the API.
  • Resolvers: Functions that handle the logic for fetching the data specified in the queries and mutations.
  • Types: Define the shape of the data and the relationships between different pieces of data.
  • Follow best practices for GraphQL, such as defining a clear schema, implementing efficient resolvers, using pagination, implementing error handling, and securing your API.

Conclusion

GraphQL provides a more efficient and flexible alternative to REST for your API. By understanding and implementing the key concepts, examples, and best practices covered in this guide, you can effectively integrate GraphQL with your Express.js applications. Happy coding!