Coursera has over 500 REST APIs in production. We're all in on GraphQL, but given this scale, it would be impossible to build a schema and write 500 custom resolvers to handle each of our endpoints. Instead, we used information we already had about our APIs (which actions are available, and request and response formats) to automatically build GraphQL resolvers and add them all to a single, unified schema. We also created a structured way for developers to define relations between models, in both forward and reverse directions, which will enable almost all pages on Coursera to be loaded via a single GraphQL query.
6. 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
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
9. 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. 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. 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. 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. 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
16. 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. 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. 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. 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. 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. 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. 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. 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.
25. 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. 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. 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.