GraphQL in production within Minutes with Serverless
Nik Graf
Sebastian Siemssen
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3419118/2000px-Star_Wars_Logo.svg.png)
https://swapi.co/
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417953/server.png)
GET api/people/1
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/2595481/tumblr_m5buj3YpWc1qchkhg.gif)
{
"name": "Luke Skywalker",
"height": "1.72 m",
"mass": "77 Kg",
"hair_color": "Blond",
"skin_color": "Caucasian",
"eye_color": "Blue",
"birth_year": "19 BBY",
"gender": "Male",
"homeworld": "http://swapi.co/api/planets/1/",
"films": [
"http://swapi.co/api/films/1/",
"http://swapi.co/api/films/2/",
"http://swapi.co/api/films/3/"
],
"species": [
"http://swapi.co/api/species/1/"
],
"vehicles": [
"http://swapi.co/api/vehicles/14/",
"http://swapi.co/api/vehicles/30/"
],
"starships": [
"http://swapi.co/api/starships/12/",
"http://swapi.co/api/starships/22/"
],
"created": "2014-12-09T13:50:51.644000Z",
"edited": "2014-12-10T13:52:43.172000Z",
"url": "http://swapi.co/api/people/1/"
}
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417953/server.png)
GET api/films/1
GET api/films/2
GET api/films/3
Often we are overfetching and underfetching at the same time causing bloated payloads and additional roundtrips
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3418363/Apple_logo_black.svg.png)
Often we are overfetching and underfetching at the same time causing bloated payloads and additional roundtrips
We can solve that
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417953/server.png)
GET api/persons-with-films/1
GET api/persons/1?include-films
GET api/persons/1?include=films,...
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
App #1
App #2
App #3
App #4
Multiple apps
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
App #1
App #2
App #3
App #4
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
Multiple versions of multiple apps
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417705/api.png)
Complexity and code debt spiraling out of control
A data querying language
Runs on arbitrary code ...
Backed by a schema ...
Based on a type system ...
Making it fully introspective ...
It runs on arbitrary code.
It is completely agnostic of your storage layer and can potentially support any backend architecture.
Anatomy of a GraphQL Server
GraphQL
Your application code
Database
Internal APIs
Remote APIs
Type definitions
Evolving the server–client relationship
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417873/phone.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417953/server.png)
Server publishes possibilities
Client specifies concrete data requirements
Schema
Each schema is an arbitrarily nested hierarchy of type definitions.
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3419074/Screenshot_from_2017-01-21_09-16-21.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3419080/Screenshot_from_2017-01-21_09-23-12.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3419085/Screenshot_from_2017-01-21_09-27-16.png)
Introspection
Exposing your own schema through the type system.
Serverless
So what is Serverless?
- Serverless Architectures
- Serverless Conference
- Serverless Framework
- Serverless Inc.
- Serverless Application Model
Serverless Architectures
Serverless architectures refer to applications that significantly depend on third-party services (knows as Backend as a Service or "BaaS") or on custom code that's run in ephemeral containers (Function as a Service or "FaaS"), the best known vendor host of which currently is AWS Lambda. By using these …
Mike Roberts
https://martinfowler.com/articles/serverless.html
![](https://s3.amazonaws.com/media-p.slid.es/uploads/639265/images/3418678/mike.jpg)
Function as a unit of application logic
How does it work?
- New instance on every invocation *
- Events trigger a function
* (somewhat)
Isn't that limiting?
Hell yeah! 🙃
Just let me code and focus on the product!
![](https://s3.amazonaws.com/media-p.slid.es/uploads/639265/images/3413285/lead_large.png)
History
Machines in
Datacenters
Virtualization
in the Cloud
Containers
in the Cloud
Serverless
in the Cloud
Benefits
- 📈 Auto scaling
- 💵 Pay per execution
- 🎪 Event Driven
Common use cases
- 🕸 Web Applications
- 📱 Mobile & IoT Backends
- 💹 Data Processing
- 🤖 Chatbots
Some limitations prevent certain use cases.
Providers
- AWS Lambda
- Google Cloud Functions
- Microsoft Functions
- IBM Openwhisk
- Auth0 Webtasks
- IronIO IronFunctions
- …
Hard Numbers
- Thomson Reuter processes 4000 requests/second
- Finra processes half a trillion validations of stock trades daily
- Vevo can handle spikes of 80x normal traffic
Challenges
- Monitoring
- Cold starts
- Development flow
- Application architecture
- Service Discovery
- Service Communication
Deployment Options
- AWS User Interface
- API calls (Serverless Framework v0, Claudia, Zappa)
- CloudFormation (Serverless Framework v1, SAM)
Lambda via the UI
![](https://s3.amazonaws.com/media-p.slid.es/uploads/639265/images/3418670/Screen_Shot_2017-01-20_at_23.42.14.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/639265/images/3418671/Screen_Shot_2017-01-20_at_23.42.21.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/639265/images/3418672/Screen_Shot_2017-01-20_at_23.44.01.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/639265/images/3418673/Screen_Shot_2017-01-20_at_23.42.31.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/639265/images/3418674/Screen_Shot_2017-01-20_at_23.42.44.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/639265/images/3418675/Screen_Shot_2017-01-20_at_23.42.47.png)
Lambda via an API call
var params = {
Code: {},
Description: "",
FunctionName: "MyFunction",
Handler: "souce_file.handler_name",
MemorySize: 128,
Publish: true,
Role: "arn:aws:iam::123456789012:role/service-role/role-name",
Runtime: "nodejs4.3",
Timeout: 15,
VpcConfig: {}
};
lambda.createFunction(params, function(err, data) {
if (err) console.log(err, err.stack);
else console.log(data);
});
Lambda via CloudFromation
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "The AWS CloudFormation template for this Serverless application",
"Resources": {
"ServerlessDeploymentBucket": {
"Type": "AWS::S3::Bucket"
},
"IamRoleLambdaExecution": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"Path": "/",
"RoleName": {
"Fn::Join": [
"-",
[
"serverless-simple-http-endpoint",
"dev",
"us-east-1",
"lambdaRole"
]
]
}
}
},
"IamPolicyLambdaExecution": {
"Type": "AWS::IAM::Policy",
"DependsOn": [
"IamRoleLambdaExecution"
],
"Properties": {
"PolicyName": {
"Fn::Join": [
"-",
[
"dev",
"serverless-simple-http-endpoint",
"lambda"
]
]
},
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream"
],
"Resource": "arn:aws:logs:us-east-1:*:*"
},
{
"Effect": "Allow",
"Action": [
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-east-1:*:*"
}
]
},
"Roles": [
{
"Ref": "IamRoleLambdaExecution"
}
]
}
},
"CurrentTimeLambdaFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "ServerlessDeploymentBucket"
},
"S3Key": "serverless/serverless-simple-http-endpoint/dev/1484930732222-2017-01-20T16:45:32.222Z/serverless-simple-http-endpoint.zip"
},
"FunctionName": "serverless-simple-http-endpoint-dev-currentTime",
"Handler": "handler.endpoint",
"MemorySize": 1024,
"Role": {
"Fn::GetAtt": [
"IamRoleLambdaExecution",
"Arn"
]
},
"Runtime": "nodejs4.3",
"Timeout": 6
},
"DependsOn": [
"IamPolicyLambdaExecution",
"IamRoleLambdaExecution"
]
},
"CurrentTimeLambdaVersionZ1TELpJKRofoApVATkwyg3d6mnjiRs3VIERorWczoY": {
"Type": "AWS::Lambda::Version",
"DeletionPolicy": "Retain",
"Properties": {
"FunctionName": {
"Ref": "CurrentTimeLambdaFunction"
},
"CodeSha256": "Z1TELpJKRofoApVATkwyg3d6mnjiRs3VIERorW/czoY="
}
},
"ApiGatewayRestApi": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Name": "dev-serverless-simple-http-endpoint"
}
},
"ApiGatewayResourcePing": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"ParentId": {
"Fn::GetAtt": [
"ApiGatewayRestApi",
"RootResourceId"
]
},
"PathPart": "ping",
"RestApiId": {
"Ref": "ApiGatewayRestApi"
}
}
},
"Roles": [
{
"Ref": "IamRoleLambdaExecution"
}
]
}
},
"CurrentTimeLambdaFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "ServerlessDeploymentBucket"
},
"S3Key": "serverless/serverless-simple-http-endpoint/dev/1484930732222-2017-01-20T16:45:32.222Z/serverless-simple-http-endpoint.zip"
},
"FunctionName": "serverless-simple-http-endpoint-dev-currentTime",
"Handler": "handler.endpoint",
"MemorySize": 1024,
"Role": {
"Fn::GetAtt": [
"IamRoleLambdaExecution",
"Arn"
]
},
"Runtime": "nodejs4.3",
"Timeout": 6
},
"DependsOn": [
"IamPolicyLambdaExecution",
"IamRoleLambdaExecution"
]
},
"CurrentTimeLambdaVersionZ1TELpJKRofoApVATkwyg3d6mnjiRs3VIERorWczoY": {
"Type": "AWS::Lambda::Version",
"DeletionPolicy": "Retain",
"Properties": {
"FunctionName": {
"Ref": "CurrentTimeLambdaFunction"
},
"CodeSha256": "Z1TELpJKRofoApVATkwyg3d6mnjiRs3VIERorW/czoY="
}
},
"ApiGatewayRestApi": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Name": "dev-serverless-simple-http-endpoint"
}
},
"ApiGatewayResourcePing": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"ParentId": {
"Fn::GetAtt": [
"ApiGatewayRestApi",
"RootResourceId"
]
},
"PathPart": "ping",
"RestApiId": {
"Ref": "ApiGatewayRestApi"
}
}
},
"ApiGatewayMethodPingGet": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"HttpMethod": "GET",
"RequestParameters": {},
"ResourceId": {
"Ref": "ApiGatewayResourcePing"
},
"RestApiId": {
"Ref": "ApiGatewayRestApi"
},
"AuthorizationType": "NONE",
"Integration": {
"IntegrationHttpMethod": "POST",
"Type": "AWS_PROXY",
"Uri": {
"Fn::Join": [
"",
[
"arn:aws:apigateway:",
{
"Ref": "AWS::Region"
},
":lambda:path/2015-03-31/functions/",
{
"Fn::GetAtt": [
"CurrentTimeLambdaFunction",
"Arn"
]
},
"/invocations"
]
]
}
},
"MethodResponses": []
}
},
"ApiGatewayDeployment1484930732234": {
"Type": "AWS::ApiGateway::Deployment",
"Properties": {
"RestApiId": {
"Ref": "ApiGatewayRestApi"
},
"StageName": "dev"
},
"DependsOn": [
"ApiGatewayMethodPingGet"
]
},
"CurrentTimeLambdaPermissionApiGateway": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"FunctionName": {
"Fn::GetAtt": [
"CurrentTimeLambdaFunction",
"Arn"
]
},
"Action": "lambda:InvokeFunction",
"Principal": "apigateway.amazonaws.com",
"SourceArn": {
"Fn::Join": [
"",
[
"arn:aws:execute-api:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":",
{
"Ref": "ApiGatewayRestApi"
},
"/*/*"
]
]
}
}
}
},
"Outputs": {
"ServerlessDeploymentBucketName": {
"Value": {
"Ref": "ServerlessDeploymentBucket"
}
},
"CurrentTimeLambdaFunctionArn": {
"Description": "Lambda function info",
"Value": {
"Fn::GetAtt": [
"CurrentTimeLambdaFunction",
"Arn"
]
}
},
"CurrentTimeLambdaFunctionQualifiedArn": {
"Description": "Current Lambda function version",
"Value": {
"Ref": "CurrentTimeLambdaVersionZ1TELpJKRofoApVATkwyg3d6mnjiRs3VIERorWczoY"
}
},
"ServiceEndpoint": {
"Description": "URL of the service endpoint",
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Ref": "ApiGatewayRestApi"
},
".execute-api.us-east-1.amazonaws.com/dev"
]
]
}
}
}
}
![](https://s3.amazonaws.com/media-p.slid.es/uploads/639265/images/3418686/Screen_Shot_2017-01-21_at_00.01.14.png)
Framework
# serverless.yml
service: serverless-simple-http-endpoint
frameworkVersion: ">=1.1.0 <2.0.0"
provider:
name: aws
runtime: nodejs4.3
functions:
currentTime:
handler: handler.endpoint
events:
- http:
path: ping
method: get
![](https://s3.amazonaws.com/media-p.slid.es/uploads/278894/images/3417296/serverless.png)
// handler.js
module.exports.endpoint = (event, context, callback) => {
const time = new Date().toTimeString();
const response = {
statusCode: 200,
body: JSON.stringify({
message: `Hello, the current time is ${time}.`,
}),
};
callback(null, response);
};
![](https://s3.amazonaws.com/media-p.slid.es/uploads/639265/images/3418809/Screen_Shot_2017-01-21_at_01.23.48.png)
Serverless Architecture
❤️
GraphQL
import {
GraphQLObjectType,
GraphQLString,
} from 'graphql';
const MeetupType = new GraphQLObjectType({
name: 'Meetup',
description: 'A gathering of people',
fields: () => ({
name: {
type: GraphQLString,
description: 'The name of the meetup',
},
description: {
type: GraphQLString,
description: 'The long description of the meetup',
},
}),
});
export default MeetupType;
// schema.js
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
meetups: {
type: new GraphQLList(MeetupType),
resolve: () => endpoints.getMeetups(),
}
}),
});
export default new GraphQLSchema({
query: QueryType,
});
// handler.js
const handle = require('./schema');
module.exports.graphql = (event, context, callback) => {
handle(event.body.query, event.body.variables)
.then((response) => callback(null, response))
.catch((error) => callback(error));
};
// getMeetups.js
import fetch from 'node-fetch';
const url = 'https://api.meetup.com/viennajs/events?photo-host=secure
&sig_id=12607916&sig=d06a49439dafad0b17b5a275f882cc1f79711430';
export default () => fetch(url).then((res) => res.json());
![](https://s3.amazonaws.com/media-p.slid.es/uploads/639265/images/3418833/wrapped-rest-api.gif)
Questions
GraphQL + Serverless
By Sebastian Siemssen
GraphQL + Serverless
- 3,025