From REST to GraphQLMarc-Andre Giroux @__xuorig__
View Slide
About me
A simple UI component
What kind of data is needed?
Cart Product ProductImage
Reusable endpoints (REST)
/carts/1/products/1/products/2/products/3/product_images/1/product_images/2/product_images/3
Too many round trips!
/carts/1?expand=products
/carts/1?fields=products(name, description, price)
/carts/1?fields=products/name,description,price
Custom Endpoints
/cart_with_all_the_stuff_i_need
/cart_with_all_the_stuff_i_need/cart_version_2_with_all_the_things/cart_with_products_and_images/cart_with_products_and_images_with_price_and_taxesmy_tightly_coupled_custom_endpoint_including_only_the_things_i_need_bla_bla_bla_bla/cart_with_products_and_images_with_price_and_taxes_but_no_description/cart_with_products_and_images_with_price_and_taxes_but_no_description_v2
Server ClientUpdates a viewCreates a new viewProduct view v2Product model changesUpdate endpointsCreate new endpoint
GraphQL
What GraphQL is NOT
What GraphQL IS
{myShop {name}} FieldSelection Set
{myShop {name}}LexedParsedValidatedExecuted{“myShop” {“name”: “GitHub”}}
{myShop {name}}
{shop(id: 1) {name}}
{myShop {namelocation {cityaddress}products(orderby: POPULARITY) {nameprice}}}
{“myShop”: {“name”: “Full Stack Fest Shop”“location” {“city”: “Barcelona”“address”: “Av. Diagonal 547”}“products”: [{“name”: “Conference Ticket”“price”: 500000}, {“name”: “Cool T-Shirt”“price”: 20000}]}}
Type System
type QueryRoot {myShop: Shopshop(id: Int): Shop}
type Shop {name: Stringlocation: Addressproducts(orderby: OrderEnum): [Product]}enum ProductOrderEnum {PRICE,POPULARITY,ALPHABETICAL}
type Address {city: Stringaddress: String}
type Product {name: Stringprice: Int}
Fragments
{myShop {namelocation {cityaddress}products(orderby: POPULARITY) {idnameprice}}}
fragment productFields on Product {idnameprice}
{myShop {namelocation {cityaddress}products(orderby: POPULARITY) {...productFields}}}
queryFragmentFragmentFragment Fragment
Introspection
query {__schema {…}}
Static ValidationCode GenerationIDE IntegrationAuto Documentation
Resolving fields
ProductType = GraphQL::ObjectType.define doname "Product"description “A product sold at a shop”# …end
field :name dotype types.Stringresolve -> (obj, args, ctx) { obj.name }end
field :price dotype types.Intresolve -> (obj, args, ctx) doobj.subtotal + obj.taxes + obj.shipping_priceendend
POST /graphql
Mutations
mutation {createProduct(name: “Nice Mug”, price: 10000) {idname}}
Drawbacksand solutions
N+1 Queries
field :image dotype ImageTyperesolve -> (product, args, ctx) doproduct.imageendendfield :products dotype [ProductType]resolve -> (shop, args, ctx) doshop.productsendend
Product Load (1.0ms) SELECT "products".* FROM "products"WHERE "products"."shop_id" = …Image Load (0.9ms) SELECT "images".* FROM "images"WHERE "images"."product_id" = …Image Load (0.2ms) SELECT "images".* FROM "images"WHERE "images"."product_id" = …Image Load (0.1ms) SELECT "images".* FROM "images"WHERE "images"."product_id" = …
Solution: Batching + Caching
field :image dotype ImageTyperesolve -> (product, args, ctx) doRecordLoader.for(Image).load(product.image_id)endend
HTTP Caching
Solution: Client Side Cache
Normalized Cache
query {shop {products {price}}}query {shop {product(id: 1) {price}}}
{root: {shop: {products: [Link.new(1)]}},1: {price: 1000}}
https://github.com/facebook/relay http://www.apollostack.com/
Security
Query DepthTimeoutsQuery Complexity
Future
Subscriptions
subscription {productInventorySubscribe {products {inventory}}}
Deferred Queries
query {shop {namedescriptionproducts {nameprice}}}
query {shop {namedescriptionproducts @defer {nameprice}}}
Thank youMarc-Andre Giroux @__xuorig__