Adding GraphQL to a RESTful API

June 2021 update: while some of the content of this article remains accurate (and hopefully useful), it is a bit of a contrived example. It assumes that there is an existing REST api running on Express that needs to be maintained, while also exposing a GraphQL endpoint. In reality, a better solution would be to keep the REST api as is, and spin up another GraphQL endpoint as a separate server without mixing the two, there is no need to have the same server handle both REST and GraphQL queries, separation of concerns is I think a good thing. Then use a RESTDataSource to query the REST endpoint from the GraphQL server.

Building a REST api

$ mkdir express-graphql
$ cd express-graphql
$ npm init -y
$ npm install express
$ mkdir src
// src/app.jsconst express = require('express');
const app = express();
const port = process.env.PORT || 3000;app.listen(port, () => {
console.log(`🏃‍♂️ on port ${port}`);
});
  • A Book has an Id and a Title
  • An Author has an Id and a Name
  • An Author can have written many books
  • A Book has one and only one author.
  • Get all authors/api/authors
  • Get one author /api/authors/:authorId
  • Get all the books an author has written /api/authors/:authorId/books
  • Get all books /api/books
  • Get one book /api/books/:bookId
  • Get the author of a book /api/books/:bookId/author
// REST route for authors
const authorRouter = express.Router();
app.use('/api/authors', authorRouter);
// REST route for books
const bookRouter = express.Router();
app.use('/api/books', bookRouter);
  • getAuthors(): return a list of authors
  • getAuthor(authorId: String): return a single author
  • getAuthorBooks(authorId: String): return all the books for an author
  • getBooks(): return a list of books
  • getBook(bookId: String): return a book
  • getBookAuthor(bookId: String): return the author of a book
// return a list of authors
authorRouter.route('/').get((req, res) => res.json(db.getAuthors()));
// return an author by authorId
authorRouter
.route('/:authorId')
.get((req, res) => res.json(db.getAuthor(req.params.authorId)));
// return a list of books for an author
authorRouter
.route('/:authorId/books')
.get((req, res) => res.json(db.getAuthorBooks(req.params.authorId)));
// a list of books
bookRouter.route('/').get((req, res) => res.json(db.getBooks()));
// a book by bookId
bookRouter
.route('/:bookId')
.get((req, res) => res.json(db.getBook(req.params.bookId)));
// the author of a book
bookRouter
.route('/:bookId/author')
.get((req, res) => res.json(db.getBookAuthor(req.params.bookId)));
const db = require('./db');

Adding GraphQL capabilities to our application

$ npm install apollo-server-express
const { ApolloServer, gql } = require('apollo-server-express');
const typeDefs = /* GraphQL */ gql`
type Author {
id: ID!
name: String
}
type Book {
id: ID!
title: String
}
`;
const typeDefs = /* GraphQL */ gql`type Query {
Authors: [Author]
Author(id: ID!): Author
Books: [Book]
Book(id: ID!): Book
}
type Author {
id: ID!
name: String
}
type Book {
id: ID!
title: String
}
`;
const typeDefs = /* GraphQL */ gql`
type Query {
Authors: [Author]
Author(id: ID!): Author
Books: [Book]
Book(id: ID!): Book
}
type Author {
id: ID!
name: String
}
type Book {
id: ID!
title: String
}
extend type Author {
books: [Book]
}
extend type Book {
author: Author
}

`;
  • Authors: [Author] :Authors() returns an array of Author
  • Author(id: ID!): Author : Author(authorId:String) return an Author
  • Books: [Book] : Books() returns an array of Book
  • Book(id: ID!): Book : Book(bookId: String) returns a book
const resolvers = {
Query: {
Authors() {
return db.getAuthors();
},
Author(_, { id }) {
return db.getAuthor(id);
},
Books() {
return db.getBooks();
},
Book(_, { id }) {
return db.getBook(id);
},
},
};
const resolvers = {
Query: {
// ...
},
Author: {
books(author) {
return db.getAuthorBooks(author.id);
},
},

};
Book: {
author(book) {
return db.getAuthor(book.authorId);
},
},
const resolvers = {
Query: {
Authors() {
return db.getAuthors();
},
Author(_, { id }) {
return db.getAuthor(id);
},
Books() {
return db.getBooks();
},
Book(_, { id }) {
return db.getBook(id);
},
},
Author: {
books(author) {
return db.getAuthorBooks(author.id);
},
},
Book: {
author(book) {
return db.getAuthor(book.authorId);
},
},
};
const server = new ApolloServer({
resolvers,
typeDefs,
});
server.applyMiddleware({ app });
query {
Authors {
name
}
Author(id: 2) {
name
}
}
query {
Authors {
id
name
}
Author(id: 2) {
id
name
}
}
query {
Authors {
name
books {
title
}

}
Author(id: 2) {
name
books {
title
}

}
}
query {
Authors {
id
name
books {
title
}
}
Author(id: 2) {
id
name
}
Books {
id
title
}
Book(id: 10) {
id
title
author {
name
}
}
}

A word of caution

const server = new ApolloServer({
resolvers,
typeDefs,
tracing: (process.env.NODE_ENV === 'development'), // tracing
});

Next Steps

  • Authors()
  • Author(id: ID!)
  • Books()
  • Book(id:ID!)

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Arnaud Dostes

Arnaud Dostes

Previously Paris and Geneva, currently New York. Can also be found at https://dev.to/arnaud