Adding GraphQL to our REST APIs
without writing a single custom resolver
Declarative GraphQL
Bryan Kane
! @bryanskane
" bryan-coursera
2
REST APIs at Coursera
4
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
584
resources
1352
endpoints
70
services
30
backend devs
Chapter 4:
Lessons

in DX
5
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Benefits of Naptime
6
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
APIs are consistent:

Every API returns data in the same format and handles requests in
the same way

Lower cost:

Building new APIs requires much lower time investment

and fewer lines-of-code required

Type safety / correctness checking:

APIs are validated for correctness at compile time
Chapter 4:
Lessons

in DX
REST isn’t perfect, even with Naptime
7
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Loading related data isn’t well defined:

Requires multiple roundtrips or one-off models with weird syntax

API discovery is hard:

If a developer can’t find an existing API, they’ll build their own
Communities are important:

Maintaining a library [and documentation] in house is expensive
Chapter 4:
Lessons

in DX
Our GraphQL Story
9
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
GraphQL is closer to perfect…
Solves almost every problem that we had with our REST APIs:

• API discoverability is much easier with GraphiQL

• Nested / related data is intuitive

• There’s a standard [and a spec] behind it
Chapter 4:
Lessons

in DX
10
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
… but getting there is harder
We don’t want to write 584 resolvers by hand.

• Developers are lazy

• Requires keeping two services in sync

• Schema design could be inconsistent
Chapter 4:
Lessons

in DX
11
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
So we automated it
Naptime gives us three important things:

• A schema that defines available endpoints

• Detailed type information about parameters

• Schema definitions for all response bodies
Chapter 4:
Lessons

in DX
12
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Adapting to GraphQL
Built a new service that could:

• Parse API schemas and build a unified GraphQL schema

• Resolve GraphQL queries against our REST APIs
Chapter 4:
Lessons

in DX
13
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Schema Design
type CoursesV1 {
id: String!
slug: String!
...more fields
}
type CoursesV1Resource {
myWatchlist(limit: Int = 100, start: String): CoursesV1Connection!
bySlug(slug: String!, limit: Int = 100, start: String): CoursesV1Connection!
getAll(limit: Int = 100, start: String): CoursesV1Connection!
get(id: String!): CoursesV1
multiGet(ids: [String!]!, limit: Int = 100, start: String): CoursesV1Connection!
}
type CoursesV1Connection {
elements: [CoursesV1]!
paging: ResponsePagination!
}
Chapter 4:
Lessons

in DX
14
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Schema Design
type CoursesV1Resource {
myWatchlist(...): CoursesV1Connection!
bySlug(...): CoursesV1Connection!
getAll(...): CoursesV1Connection!
get(...): CoursesV1
multiGet(...): CoursesV1Connection!
}
type root {
coursesWatchlist(...): CoursesV1Connection!
courseBySlug(...): CoursesV1Connection!
allCourses(...): CoursesV1Connection!
course(...): CoursesV1
courses(...): CoursesV1Connection!
}
Linking It All Together
15
16
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Data Relations are Powerful
query CoursePageQuery {
CoursesV1Resource {
course(limit: 123) {
instructor {
name
university {
slug
country
}
}
myEnrollments {
grade
}
}
}
}
17
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Backend APIs are a black box
• Joining arbitrary data is easy in a relational database,

joining across APIs is harder

• We have no control over backend systems
• Most data is stored in Cassandra, a NoSQL database
• This requires explicit indexes on data for lookups

• We want to keep services as independent as possible
18
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Three types of relationships
model A knows about model B
Course Instructor
model Course {
id
slug
instructorIds
}
model Instructor {
id
name
}
19
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Three types of relationships
Course Instructor
model Course {
id
slug
}
model A doesn’t know about model B

model B knows about model A
model Instructor {
id
name
courseIds
}
20
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Three types of relationships
Course Instructor
model Course {
id
slug
}
model A doesn’t know about model B

model B doesn’t know about model A
model Instructor {
id
name
}
21
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Forward Relationships
These are easy — just fetch the data you need by ID
courseResource.withRelations(
"instructors" -> MultiGetRelation(
resourceName = "instructors.v1",
ids = “$instructorIds",
arguments = Map("includeHidden" -> "true"))
22
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Reverse Relationships
• Define an endpoint on resource B to lookup by model A
• Define in resource A how to look up on resource B
resource.withRelations(
"instructors" -> FinderReverseRelation(
resourceName = "instructors.v1",
finderName = “byCourseId",
arguments = Map("courseId" -> "$id"))
23
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Resources with no connection
Define a new resource that can link the two resources together,
and use a reverse relationship.
Learnings in

Developer Experience
25
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Declarative over Imperative
• On the client, GraphQL is great for defining your data needs, not
how to fetch the data

• We took the same approach for adding GraphQL and relations
on the backend:
• Developers define the structure of their data and
relationships, but not how to fetch the data
26
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Large migrations are expensive
• It takes many resources to convert our client API calls

from REST to GraphQL.

• If we had to pay another cost on the backend for each resource,
migrating to GraphQL would be too expensive
27
Chapter 1:
Coursera’s

REST APIs
Chapter 2:
Adapting to
GraphQL
Chapter 3:
Linking the
Resources
Chapter 4:
Lessons

in DX
Figure out schemas before building products
• We initially built out our assembler service without support for
reverse relations.

• If we had defined our ideal schemas for products before building
anything, we would’ve seen that this was a requirement.
Thanks!
Bryan Kane
! @bryanskane
" bryan-coursera

Declarative GraphQL: Adding GraphQL to our REST APIs without writing a single custom resolver

  • 1.
    Adding GraphQL toour REST APIs without writing a single custom resolver Declarative GraphQL Bryan Kane ! @bryanskane " bryan-coursera
  • 2.
  • 3.
    REST APIs atCoursera
  • 4.
    4 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources 584 resources 1352 endpoints 70 services 30 backend devs Chapter 4: Lessons
 in DX
  • 5.
    5 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX
  • 6.
    Benefits of Naptime 6 Chapter1: Coursera’s
 REST APIs Chapter 2: Adapting to GraphQL Chapter 3: Linking the Resources APIs are consistent:
 Every API returns data in the same format and handles requests in the same way
 Lower cost:
 Building new APIs requires much lower time investment
 and fewer lines-of-code required
 Type safety / correctness checking:
 APIs are validated for correctness at compile time Chapter 4: Lessons
 in DX
  • 7.
    REST isn’t perfect,even with Naptime 7 Chapter 1: Coursera’s
 REST APIs Chapter 2: Adapting to GraphQL Chapter 3: Linking the Resources Loading related data isn’t well defined:
 Requires multiple roundtrips or one-off models with weird syntax
 API discovery is hard:
 If a developer can’t find an existing API, they’ll build their own Communities are important:
 Maintaining a library [and documentation] in house is expensive Chapter 4: Lessons
 in DX
  • 8.
  • 9.
    9 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources GraphQL is closer to perfect… Solves almost every problem that we had with our REST APIs:
 • API discoverability is much easier with GraphiQL
 • Nested / related data is intuitive
 • There’s a standard [and a spec] behind it Chapter 4: Lessons
 in DX
  • 10.
    10 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources … but getting there is harder We don’t want to write 584 resolvers by hand.
 • Developers are lazy
 • Requires keeping two services in sync
 • Schema design could be inconsistent Chapter 4: Lessons
 in DX
  • 11.
    11 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources So we automated it Naptime gives us three important things:
 • A schema that defines available endpoints
 • Detailed type information about parameters
 • Schema definitions for all response bodies Chapter 4: Lessons
 in DX
  • 12.
    12 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Adapting to GraphQL Built a new service that could:
 • Parse API schemas and build a unified GraphQL schema
 • Resolve GraphQL queries against our REST APIs Chapter 4: Lessons
 in DX
  • 13.
    13 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Schema Design type CoursesV1 { id: String! slug: String! ...more fields } type CoursesV1Resource { myWatchlist(limit: Int = 100, start: String): CoursesV1Connection! bySlug(slug: String!, limit: Int = 100, start: String): CoursesV1Connection! getAll(limit: Int = 100, start: String): CoursesV1Connection! get(id: String!): CoursesV1 multiGet(ids: [String!]!, limit: Int = 100, start: String): CoursesV1Connection! } type CoursesV1Connection { elements: [CoursesV1]! paging: ResponsePagination! } Chapter 4: Lessons
 in DX
  • 14.
    14 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX Schema Design type CoursesV1Resource { myWatchlist(...): CoursesV1Connection! bySlug(...): CoursesV1Connection! getAll(...): CoursesV1Connection! get(...): CoursesV1 multiGet(...): CoursesV1Connection! } type root { coursesWatchlist(...): CoursesV1Connection! courseBySlug(...): CoursesV1Connection! allCourses(...): CoursesV1Connection! course(...): CoursesV1 courses(...): CoursesV1Connection! }
  • 15.
    Linking It AllTogether 15
  • 16.
    16 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX Data Relations are Powerful query CoursePageQuery { CoursesV1Resource { course(limit: 123) { instructor { name university { slug country } } myEnrollments { grade } } } }
  • 17.
    17 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX Backend APIs are a black box • Joining arbitrary data is easy in a relational database,
 joining across APIs is harder
 • We have no control over backend systems • Most data is stored in Cassandra, a NoSQL database • This requires explicit indexes on data for lookups
 • We want to keep services as independent as possible
  • 18.
    18 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX Three types of relationships model A knows about model B Course Instructor model Course { id slug instructorIds } model Instructor { id name }
  • 19.
    19 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX Three types of relationships Course Instructor model Course { id slug } model A doesn’t know about model B
 model B knows about model A model Instructor { id name courseIds }
  • 20.
    20 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX Three types of relationships Course Instructor model Course { id slug } model A doesn’t know about model B
 model B doesn’t know about model A model Instructor { id name }
  • 21.
    21 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX Forward Relationships These are easy — just fetch the data you need by ID courseResource.withRelations( "instructors" -> MultiGetRelation( resourceName = "instructors.v1", ids = “$instructorIds", arguments = Map("includeHidden" -> "true"))
  • 22.
    22 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX Reverse Relationships • Define an endpoint on resource B to lookup by model A • Define in resource A how to look up on resource B resource.withRelations( "instructors" -> FinderReverseRelation( resourceName = "instructors.v1", finderName = “byCourseId", arguments = Map("courseId" -> "$id"))
  • 23.
    23 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX Resources with no connection Define a new resource that can link the two resources together, and use a reverse relationship.
  • 24.
  • 25.
    25 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX Declarative over Imperative • On the client, GraphQL is great for defining your data needs, not how to fetch the data
 • We took the same approach for adding GraphQL and relations on the backend: • Developers define the structure of their data and relationships, but not how to fetch the data
  • 26.
    26 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX Large migrations are expensive • It takes many resources to convert our client API calls
 from REST to GraphQL.
 • If we had to pay another cost on the backend for each resource, migrating to GraphQL would be too expensive
  • 27.
    27 Chapter 1: Coursera’s
 REST APIs Chapter2: Adapting to GraphQL Chapter 3: Linking the Resources Chapter 4: Lessons
 in DX Figure out schemas before building products • We initially built out our assembler service without support for reverse relations.
 • If we had defined our ideal schemas for products before building anything, we would’ve seen that this was a requirement.
  • 29.