Skip to content

Wrapping a REST API in GraphQL #379

Closed
@dalgard

Description

@dalgard

The article Wrapping a REST API in GraphQL describes how to use a client-side schema to resolve queries against a REST endpoint.

The schema is inserted as a network layer in Relay – can something similar be achieved with Apollo Client?

Activity

dalgard

dalgard commented on Jul 11, 2016

@dalgard
Author

Seems easier than I expected: Custom network interface

stubailo

stubailo commented on Jul 11, 2016

@stubailo
Contributor

Yep, that's it! Very similar to the Relay approach.

linonetwo

linonetwo commented on Aug 19, 2016

@linonetwo

Great, I'm currently seeking for this. @dalgard Have you tried it yet?

stubailo

stubailo commented on Aug 19, 2016

@stubailo
Contributor

By the way, if someone wants to write a post about this it would be awesome!

dalgard

dalgard commented on Aug 23, 2016

@dalgard
Author

@linonetwo Not yet. Don't know whether I'm going to need it within the next couple of months. Still interested, though!

linonetwo

linonetwo commented on Aug 24, 2016

@linonetwo

OK I'm trying it...

what can be inferred from apollo's docs is just:

import { addQueryMerging } from 'apollo-client';

class REST2GraphQLInterface {

  query({ query, variables, debugName }) {
    return Promise.resolve({ data: {}, errors: [{ code: 0, msg: 'errorMsg' }] });
  }
}

export default addQueryMerging(REST2GraphQLInterface);

I need to refer to relay's doc for further implementation examples...

linonetwo

linonetwo commented on Aug 24, 2016

@linonetwo

So I don't need a 「graphql」 package, instead a 「graphql-tool」 at the client side?

linonetwo

linonetwo commented on Aug 24, 2016

@linonetwo

Well, I think writing REST2GraphQL on the client side is much similar to what we usually wrote on the server side.

The only difference is that we use the connector to get data from database usually, now we use the connector to get data from REST endpoint.

I'm not sure whether I'm on the right path or not.

stubailo

stubailo commented on Aug 24, 2016

@stubailo
Contributor

@linonetwo you're definitely on the right path. What you are basically doing is writing a "GraphQL server", but on the client, so any of the GraphQL server docs will apply.

You don't need apollo-server or express-graphql for this - those packages are for attaching GraphQL to a real web server, which you don't have or need on the client. You just need to get a GraphQLSchema instance and call it yourself via the graphql function from the graphql package.

linonetwo

linonetwo commented on Aug 25, 2016

@linonetwo

Thanks for guiding.

Another question: One REST endpoint would give me tons of data. Typically if I want to fill in blanks for this type;

# /api/account/whoami
type User {
  login: Boolean!
  username: String!
  password: String!

  id: Int!
  name: String!

  companyId: Int
  companyName: String
  departmentId: Int
  departmentName: String
  role: String
}

I will write resolver functions, each will just getting small piece of data.
though /api/account/whoami will return

{
  "code": 0,
  "data": {
    "companyId": 1,
    "companyName": "Company1",
    "departmentId": 1,
    "departmentName": "工程部",
    "id": 7,
    "name": "用户3",
    "role": "Customer",
    "username": "Customer3"
  }
}

which covers many of fields of User type.

should I still write resolving functions for every field?
And addQueryMerging(REST2GraphQLInterface) will do some magic for this?

I think I can cache data inside connector.
if /api/account/whoami returns

{
  "code": 0,
  "data": {
    "companyId": 1,
    "companyName": "Company1",
    "departmentId": 1,
    "departmentName": "工程部",
    "id": 7,
    "name": "用户3",
    "role": "Customer",
    "username": "Customer3"
  }
}

and I only need departmentName and companyName for this time, I can cache the whole json inside User model, and set an expire time or so. When next query about role goto User model will hit this cache .

But it looks redundantly with apollo-client 's client side cache.

So my opinion is to simulate the batching again here. If two queries come together within 10ms, second one will hit the cache.

Another option is caching at Model level and use graphQL decorator or so, to pass an 'force fetch' argument to this level. In this way, "client side server" 's caching won't be obscure to user.

Which way is better still need experiment.

linonetwo

linonetwo commented on Aug 25, 2016

@linonetwo

I will write a post after this...

I'm currently using「graphql」package to parse executeableSchema like this:

const executableSchema = makeExecutableSchema({
  typeDefs: schema,
  resolvers,
});

class REST2GraphQLInterface {
  query({ query, variables }) {
    return graphql(
      executableSchema,
      query,
      undefined, // should be rootValue here, I don'k know what its used for
      { 
        Config: new Config({ connector: serverConnector }),
        User: new User({ connector: serverConnector }),
      },
      variables
    );
  }
}

export default addQueryMerging(new REST2GraphQLInterface());

And it doesn't work, throwing Error: Must provide a schema definition from \node_modules\graphql\utilities\buildASTSchema.js

Maybe executableSchema is not what it wants?

stubailo

stubailo commented on Aug 26, 2016

@stubailo
Contributor

What is schema? perhaps it's not the right kind of object?

stubailo

stubailo commented on Aug 26, 2016

@stubailo
Contributor

Also, you don't need to write resolvers like ({ field }) => field - that's the default behavior. So you only need explicit resolvers for the case where you need to do some transformation on the data.

linonetwo

linonetwo commented on Aug 28, 2016

@linonetwo

OK, My only reference is GitHunt... Learnt from it :

type Entry {
  repository: Repository!
  postedBy: User!
  createdAt: Float! # Actually a date
  score: Int!
  comments: [Comment]! # Should this be paginated?
  commentCount: Int!
  id: Int!
  vote: Vote!
}
`];

export const resolvers = {
  Entry: {
    repository({ repository_name }, _, context) {
      return context.Repositories.getByFullName(repository_name);
    },
    postedBy({ posted_by }, _, context) {
      return context.Users.getByLogin(posted_by);
    },
    comments({ repository_name }, _, context) {
      return context.Comments.getCommentsByRepoName(repository_name);
    },
    createdAt: property('created_at'),
    commentCount({ repository_name }, _, context) {
      return context.Comments.getCommentCount(repository_name) || constant(0);
    },
    vote({ repository_name }, _, context) {
      if (!context.user) return { vote_value: 0 };
      return context.Entries.haveVotedForEntry(repository_name, context.user.login);
    },
  },

that
every field we are using should a resolver function, or we can't get data for it.

Are there any edge case learning material?

8 remaining items

Urigo

Urigo commented on Feb 7, 2017

@Urigo
Contributor

@linonetwo I would love to see an English version of that post!
If you need help with that I would love too

linonetwo

linonetwo commented on Feb 12, 2017

@linonetwo

@Urigo Ok, I'm working on that, please wait for a while.

linonetwo

linonetwo commented on Feb 12, 2017

@linonetwo

@Urigo http://onetwo.ren/wrapping-restful-in-graphql/

Were there any bug or issue please point out. Any question, ask me and I will add some content. But what I want to say is that

Setting up an REST-GraphQL gateway is quite similar to set up a simple and naive GraphQL server

lucasconstantino

lucasconstantino commented on Mar 14, 2017

@lucasconstantino
Contributor

I've being working on a project with apollo-client/server both on the browser wrapping a rest api. I works smoothly, with File upload support, Apollo client dev tools working properly, etc, etc. I'm working on a post in portuguese, but I guess I can make a copy in english and put in on Medium or similar. Does anyone have a better or more contextualized place to publish it, in case it fits?

Urigo

Urigo commented on Mar 15, 2017

@Urigo
Contributor

@lucasconstantino Medium sounds great.
If you want we can review it and in the case will fit and a lot of people will be interested, publish it also under our Apollo publication

lucasconstantino

lucasconstantino commented on Mar 29, 2017

@lucasconstantino
Contributor
SeanBannister

SeanBannister commented on Jul 7, 2017

@SeanBannister

For anyone else stumbling across this you might like to check out https://github.com/mstn/apollo-local-network-interface

jozanza

jozanza commented on Nov 22, 2017

@jozanza

Looks like the approach with Apollo 2.0 is different with the "link" approach replacing network interfaces. Here's how I personally got my plain old GraphQLSchema instance working:

import { Observable, ApolloLink, execute, makePromise } from 'apollo-link'
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'
import { ApolloClient } from 'apollo-client'
import { graphql, print } from 'graphql'
import schema from './schema' // should be an instance of GraphQLSchema

const link = new ApolloLink(
  operation =>
    new Observable(observer => {
      const { query, variables, operationName } = operation
      graphql(schema, print(query), {}, {}, variables, operationName)
        .then(result => {
          observer.next(result)
          observer.complete(result)
        })
        .catch(e => observer.error(e))
    }),
)
const cache = new InMemoryCache()
//  Note: if your schema contains interfaces / union types, ApolloClient can't infer possibleTypes out-of-the-box
//  You'll have to let it know manually via a fragmentMatcher. For example:
//  const cache = new InMemoryCache({
//    fragmentMatcher: new IntrospectionFragmentMatcher({
//      introspectionQueryResultData: {
//        __schema: {
//          types: [{
//            kind: 'INTERFACE',
//            name: 'Either',
//            possibleTypes: [
//               { name: 'Left' },
//               { name: 'Right' },
//            ]
//          }] 
//        }
//      },
//    }),
//  })
const client = new ApolloClient({ link, cache })
Vanuan

Vanuan commented on Jan 3, 2018

@Vanuan

Here's an example for Apollo client 2.0:

https://stackoverflow.com/a/48082522/99024

locked as resolved and limited conversation to collaborators on Feb 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @SeanBannister@Vanuan@stubailo@jozanza@dalgard

        Issue actions

          Wrapping a REST API in GraphQL · Issue #379 · apollographql/apollo-client