2D Head Visualizations¶
2D topographic head map¶
The 2D topographic head map provides a view of voltage measurements as a heatmap imposed on an image of a 2D skull. It can be generated as an animation to view changes over time or as a standalone plot.
General Setup¶
Import required modules¶
from simpl_eeg import topomap_2d, eeg_objects
import warnings
warnings.filterwarnings('ignore')
Note
Please include the line below in your IDE so that the changes would be simultaneously reflected when you make a change to the python scripts.
%load_ext autoreload
%autoreload 2
Create epoched data¶
For additional options see Creating EEG Objects section.
experiment_folder = "../../data/109"
nth_epoch = 0
num_steps = 50 # number of steps to skip to shorten epoch
epochs = eeg_objects.Epochs(experiment_folder)
epoch = epochs.get_epoch(nth_epoch)
shortened_epoch = epochs.skip_n_steps(num_steps)
Reading /Users/sasha/mds/simpl_eeg_capstone/data/109/fixica.fdt
Not setting metadata
Not setting metadata
33 matching events found
Setting baseline interval to [-0.2998046875, 0.0] sec
Applying baseline correction (mode: mean)
0 projection items activated
Loading data for 33 events and 2049 original time points ...
0 bad epochs dropped
Create a 2D topographic animation¶
Define parameters¶
A detailed description of all animation parameters can be found in the topomap_2d.animate_topomap_2d
docstring:
help(topomap_2d.animate_topomap_2d)
Help on function animate_topomap_2d in module simpl_eeg.topomap_2d:
animate_topomap_2d(epoch, plotting_data=None, colormap='RdBu_r', mark='dot', cmin=-30, cmax=30, colorbar=True, timestamp=True, frame_rate=12, **kwargs)
Plots a still image mne.epochs.Epochs EEG data as a 2D topomap using the mne.viz.plot_topomap
function.
Parameters:
epochs: mne.epochs.Epochs
MNE epochs object containing portions of raw EEG data built around specified timestamp(s)
colormap: matplotlib colormap or None
Specifies the 'colormap' parameter in the mne.viz.plot_topomap() function.
mark: str
Specifies what kind of marker should be shown for each node on the topomap. Can be one of
'dot', 'r+' (for red +'s), 'channel_name', or 'none'.
cmin: float
Specifies the 'vmin' parameter in the mne.viz.plot_topomap() function.
Sets the limits for the colors which will be used on the topomap. Value is
in μV. Defaults to -10.
cmax: float
Specifies the 'vmax' parameter in the mne.viz.plot_topomap() function.
Sets the limits for the colors which will be used on the topomap. Value is
in μV. Defaults to +10.
res: int
Specifies the 'res' and parameters in the mne.viz.plot_topomap() function. "The
resolution of the topomap image (n pixels along each side)."
colorbar: bool
Specifies whether or not to include a colorbar in the animation.
timestamp: bool
Specifies whether or not to show the timestamp on the plot relative to the time in the epoch that
is being shown. Defaults to True.
frame_rate: int or float
The frame rate to genearte the final animation with.
**kwargs: various
Additional arguments from the 'mne.viz.plot_topomap' function
(https://mne.tools/stable/generated/mne.viz.plot_topomap.html) may be provided. Since this function
is used under the hood most of these arguments should work exactly as they do in their original
function. The exact list of avalible arguments includes: [contours, sphere, res, extrapolate, outlines,
mask, mask_params, image_interp, show, onselect, border, ch_type]. The 'axes' argument is not avalible
since it does not work with animations.
Returns:
ani: matplotlib.animation.FuncAnimation
matplotlib funcanimation of a 2d topographic map based on the input epoch data
# change values below to values of interest
cmin = -40
cmax = 40
colormap = "Spectral"
mark = "dot"
contours = "6"
sphere = 100
res = 100
extrapolate = "head"
outlines = "head"
axes = None
mask = None
mask_params = None
colorbar = True
timestamp = True
frame_rate = 12
Generating the animation¶
%%capture
anim = topomap_2d.animate_topomap_2d(
shortened_epoch,
colormap=colormap,
mark=mark,
contours=contours,
sphere=sphere,
cmin=cmin,
cmax=cmax,
res=res,
extrapolate=extrapolate,
outlines=outlines,
axes=axes,
mask=mask,
mask_params=mask_params,
colorbar=colorbar,
timestamp=timestamp,
frame_rate=frame_rate,
)
from IPython.core.display import HTML
html_plot = anim.to_jshtml()
video = HTML(html_plot)
video
Saving the animation¶
Save as html¶
html_file_path = "examples/topo_2d.html"
html_file = open(html_file_path, "w")
html_file.write(html_plot)
html_file.close()
Save as gif¶
anim = topomap_2d.animate_topomap_2d(shortened_epoch)
gif_file_path = "examples/topo_2d.gif"
anim.save(gif_file_path, fps=5, dpi=300)
Save as mp4¶
anim = topomap_2d.animate_topomap_2d(shortened_epoch)
mp4_file_path = "examples/topo_2d.mp4"
anim.save(mp4_file_path)
Note
If FFMpegWriter
does not work on your computer you can save the file as a gif first and then convert it into mp4 file by running the code below.
import moviepy.editor as mp
clip = mp.VideoFileClip(gif_file_path)
clip.write_videofile(mp4_file_path)
Create a 2D topographic plot¶
Define parameters¶
A detailed description of all animation parameters can be found in the topomap_2d.plot_topomap_2d
docstring:
help(topomap_2d.plot_topomap_2d)
Help on function plot_topomap_2d in module simpl_eeg.topomap_2d:
plot_topomap_2d(epoch, plotting_data=None, recording_number=0, colormap='RdBu_r', colorbar=True, cmin=-30, cmax=30, mark='dot', timestamp=True, **kwargs)
Plots a still image mne.epochs.Epochs EEG data as a 2D topomap using the mne.viz.plot_topomap
function.
Parameters:
epoch: mne.epochs.Epochs
MNE epochs object containing portions of raw EEG data built around specified timestamp(s)
plotting_data: numpy.ndarray or None
Array of the EEG data from a measurement (not a time interval) to be plotted should be in
the format (num_channels, ) i.e. a single row of values for the only timestamp you plan
to plot.
recording_number: int
The "frame" of the epoch to show in the plot.
colormap: matplotlib colormap or None
Specifies the 'colormap' parameter in the mne.viz.plot_topomap() function.
colorbar: bool
Specifies whether to include a colorbar or not. Removing it will improve performance.
cmin: float
Specifies the 'vmin' parameter in the mne.viz.plot_topomap() function.
Sets the limits for the colors which will be used on the topomap. Value is
in μV. Defaults to -10.
cmax: float
Specifies the 'vmax' parameter in the mne.viz.plot_topomap() function.
Sets the limits for the colors which will be used on the topomap. Value is
in μV. Defaults to +10.
mark: str
Specifies what kind of marker should be shown for each node on the topomap. Can be one of
'dot', 'r+' (for red +'s), 'channel_name', or 'none'.
timestamp: bool
Specifies whether or not to show the timestamp on the plot relative to the time in the epoch that
is being shown. Defaults to true.
**kwargs: various
Additional arguments from the 'mne.viz.plot_topomap' function
(https://mne.tools/stable/generated/mne.viz.plot_topomap.html) may be provided. Since this function
is used under the hood most of these arguments should work exactly as they do in their original
function. The exact list of avalible arguments includes: [contours, sphere, res, extrapolate, outlines,
axes, mask, mask_params, image_interp, show, onselect, border, ch_type].
Returns:
topo_2d_fig: matplotlib.image.AxesImage
matplotlib image plot of a 2d topographic map based on the input epoch data
timestamp=True
Generating a standalone plot¶
Note
Generating a plot will use the first frame in the epoch, so make sure to update your epoch object to contain your frame of interest.
plot = topomap_2d.plot_topomap_2d(
epoch,
timestamp=timestamp
)
plot;
Saving the plot¶
You can change the file to different formats by changing the format argument in the function. It supports png
, pdf
, svg
.
file_path = "examples/topo_2d.svg"
plot.figure.savefig(file_path)


Introduction
The code snippets in this sidebar are designed to work in your apps directly. The requests are performed using the following resources.
GraphQL API:
Email address:
Welcome! Scaphold is a backend-as-a-service platform that bundles all the tools you need to quickly build production-grade applications. In a matter of minutes, you can create your own customizable GraphQL API backed by our highly available cloud infrastructure hosted on AWS.
As soon as you create an app on Scaphold, you’ll instantly have a persistent data store, data modeling tools, advanced access control management, and monitoring dashboards all at your fingertips. This will get you most of the way to launching your app, but why stop there?
Scaphold also provides two powerful features that will accelerate your team’s app development like never before:
Real-time: With GraphQL Subscriptions enabled right out of the box, you can build advanced applications like chat apps or games without having to spend any time building infrastructure to support it. Your users will immediately feel an improvement in their experience on your app with millisecond subscription latencies and by reducing the overall number of requests your app sends whenever there’s an update.
Integrations: Our integrations allow you to extend the functionality of your core API with push notifications, payments, email, social authentication, and much more. We built Scaphold to power real workloads and that means allowing you to use the same tools you already love.
New to GraphQL? Learn about it here!
Already an expert? Get started now!
Have any questions? Join our Slack!
Setup
In Scaphold’s documentation, we’ll be using the most common base networking library for each language. There are no tricks up our sleeves, it just works!
npm install --save request
GraphQL is a truly versatile query language for your API. This means that you can use any client networking library to make requests against your GraphQL API. Whether you’re building a Web, iOS, Android, or IOT app, GraphQL’s got you covered.
In short,
- A
query
is astring
- The
variables
are injson
format
Why GraphQL?
Describe your data
type Mission {
id: ID!
codename: String
astronauts: [User]
}
Ask for what you want
{
getMission(id: "abc123") {
codename
}
}
Get predictable results
{
"getMission": {
"codename": "GraphQL Takes Over The World!"
}
}
GraphQL was developed by Facebook over the years to power and scale many of their mobile and web applications. A great deal of care was taken to ensure that GraphQL works from any client platform which means you can use Scaphold to build an application on virtually any platform. This means you can target your Scaphold API from iOS, Android, Web, and IOT applications using any framework without ever having to download an SDK! GraphQL presents a better way to build applications. It’s that simple. You focus on what makes your app awesome and let us worry about the rest!
Plus GraphQL is a massive improvement on REST. Here is why!
- A declarative type system helps create clean, structured, and safe APIs.
- Write your queries once and run them anywhere! The intuitive GraphQL language is not only more powerful but also 100% cross-platform. No SDKs necessary!
- Introspection lets you build documentation into the API itself. Your API easier to learn and eliminates the need to maintain external docs.
- A GraphQL API has one endpoint. No more tricky RESTful urls.
- Client driven. GraphQL empowers those who understand their requirements best. You!
- It integrates seamlessly with the hottest frontend frameworks like React and Angular 2.0 using Relay and Apollo Client
- And many many more!
Learn GraphQL
Resources
Facebook’s GraphQL.org
Kadira’s LearnGraphQL.com
Apollo’s GraphQL.com
Clay Allsopp’s GraphQLHub.com
If you have other additions to this list that you’d like to add, feel free to contact us at our Slack channel. Message either @vince or @michael.
Look Out For…
- More public GraphQL APIs
- Relay 2.0!
- GraphQL Subscriptions updates
- Open source tooling released by companies that are using it internally
- More mobile support
Core Data
Scaphold’s core data platform allows you to easily define complex data models that are instantly deployed to a production GraphQL API backed by a highly available SQL cluster. Core Data provides a great set of tools for powering core application logic.
Every type in your schema that implements the Node
interface is backed by core data. That means every
type that implements Node
maps to a single table and can be related to any other core data types via
Connection
fields. Each core data type X
also receives a createX
, updateX
, and deleteX
mutation as well
as various ways to read the data with getX
, allX
, and through connections.
Scaphold allows you to write GraphQL queries that are compiled down to SQL which means you get powerful
filtering abilities with WhereArgs
as well as compound orderBy
expressions. We even expose the ability
for you to index certain fields in your data so that you can optimize the queries that are important to your
application.
The Schema
Example schema
type ScapholdSchema {
id: ID!
name: String!
description: String
types: [ScapholdType]
}
type ScapholdType {
id: ID!
name: String!
description: String
kind: "OBJECT" | "ENUM" | "INTERFACE"
values: [String] # if ENUM
fields: [ScapholdField] # if INTERFACE or OBJECT
interfaces: [String] # if OBJECT
permissions: [ScapholdPermission]
}
type ScapholdField {
name: String!
description: String
type: String!
ofType: String
nonNull: Boolean
unique: Boolean
reverseName: String
}
interface Node {
id: ID!
}
Every application you build on Scaphold will have a schema. A schema consists of a set of types and a set of connections between those types. This connected web of types creates the ‘graph’ that will come to define your API.
One of GraphQL’s biggest benefits is that it provides a mechanism to express types at the API level. Types have existed in general purpose programming languages and other query languages like SQL for years so it’s about time we brought it to APIs.
There are two main classes of types that you need to be aware of on Scaphold.
Types that implement the
Node
interface. These are first-class entities in your Scaphold API. We will generate queries and mutations for them, you may create connections between them, and you may use them with event-based integrations.Types that don’t implement the
Node
interface. These are auxiliary structures in your GraphQL API and these objects are stored inline within otherNode
types. Scaphold will not create queries and mutations for non-node types nor can you use them to fire event-based integrations. You can, however, still relate them to other objects via theList
type or as an inline object.
Scaphold’s Schema Designer allows you to define complex schemas in a fraction of the
time while also offering you a convenient place to setup access control rules (permissions) for your
data. As you define the structure of your schema we’ll generate the backend that will host your custom GraphQL API. By
default we generate a get
, create
, update
, and delete
operation for each type in your schema. And to give you that
extra boost, we also create mutations to handle user authentication, schema migration, and integration management.
Scalars
| Name | Description |
|---|---|
| ID | The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable. |
| String | A UTF‐8 character sequence |
| Text | Character sequence with unlimited length (cannot be indexed or have a default value) |
| Int | A signed 32-bit integer |
| Float | A signed double-precision floating-point value |
| Boolean | true or false |
| DateTime | Valid timestamp that can be converted to standard ISO 8601 date time format. |
Types
type Computer {
id: ID!
name: String!
brand: String
memory: Float
diskSpace: Float
numCores: Int
price: Float
isNew: Boolean
}
interface Animal {
name: String
height: Float
weight: Float
}
enum HouseTypeEnum {
condominium
apartment
house
duplex
}
| Name | Description |
|---|---|
| Object | These are the bread and butter of the GraphQL type system and schema. They are the model definitions of your data, telling the GraphQL server what types exist, the fields that belong to a type, and the complex relations between your data. GraphQL queries are validated against this type system. Scaphold allows you to easily define your types without having to write any server-side code, so you can leverage the benefits of GraphQL from your client side, without having to write this all yourself. |
| Interface | An interface is an abstract type that must be implemented by a type. The fields belonging to an interface must also exist on the type that implements it. |
| Enum | Enumerations are special scalar types that restrict values to a finite set of data. |
Adding a Type
In the Schema Designer tab once you’ve created your app, click the dropdown on the top right to add a type.
Type Modifiers
| Name | Description |
|---|---|
| List | A List indicates that this field will return an array of a particular type. This is denoted in GraphQL as [ ... ] surrounding the name of a type or scalar. |
| Non-Null | The Non-Null type modifier can also be used when defining arguments for a field, which will cause the GraphQL server to return a validation error if a null value is passed as that argument, whether in the GraphQL string or in the variables. This is denoted in GraphQL as a ! following the name of a type or scalar. |
Fields
In GraphQL, like many other type systems, each type will have a set of fields associated with it. Fields are used to describe a particular type, and types in Scaphold will come by default
with an id
, createdAt
, and modifiedAt
fields as part of implementing the Node
and Timestamped
interfaces. These can be removed when creating a new type; however, a type must have at least one field.
Adding a Field
In the Schema Designer tab once you’ve created your type, click the dropdown on the top right of a Type panel.
Queries
Example
getX
query
curl -X POST https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
-H "Content-Type: application/json" \
-d '{"query": "query GetUser ($input: ID!) { getUser (id: $input) { id, username, createdAt, modifiedAt, lastLogin } }",
"variables": {"input": "VXNlcjoxMQ=="}}'
import request from 'request';
const data = {
"query": `query GetUser ($input: ID!) {
getUser (id: $input) {
id
username
createdAt
modifiedAt
lastLogin
}
}`,
"variables": {
"input": "VXNlcjoxMQ=="
}
};
request({
url: "https://us-west-2.api.scaphold.io/graphql/scaphold-graphql",
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: data
}, (error, response, body) => {
if (!error && response.statusCode == 200) {
console.log(JSON.stringify(body, null, 2));
} else {
console.log(error);
console.log(response.statusCode);
}
});
The above command returns an object structured like this:
{
"data": {
"getUser": {
"id": "VXNlcjoxMQ==",
"username": "Elon Musk",
"createdAt": "2016-12-08T12:33:56.000Z",
"modifiedAt": "2016-12-08T12:33:56.000Z",
"lastLogin": "2016-12-08T12:33:56.000Z"
}
}
}
Queries allow you to read data from your GraphQL API!
Each core data type X
gets its own queries getX
and viewer.allXs
. Since core data types can be involved in
connections, you can also read related objects through any connection fields in your schema. All core data connections
take the XWhereArgs
and XOrderByArgs
inputs that allow you to do complex filtering and compound ordering.
Viewer
Example
viewer
query
curl -X POST https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
-H "Content-Type: application/json" \
-d '{ "query": "query GetAllUsers($first: Int, $after: String, $orderBy: [UserOrderByArgs]) { viewer { allUsers(first: $first, after: $after, orderBy: $orderBy) { edges { cursor node { username createdAt } } } } }",
"variables": { "first": 3, "after": "Yzp7ImNyZWF0ZWRBdCI6IjIwMTYtMTItMDhUMTU6MDI6MzguMDAwWiIsImlkIjo2fQ==", "orderBy": { "field": "createdAt", "direction": "DESC" } } }'
import request from 'request';
const data = {
"query": `query GetAllUsers($first: Int, $after: String, $orderBy: [UserOrderByArgs]) {
viewer {
allUsers(first: $first, after: $after, orderBy: $orderBy) {
edges {
cursor
node {
username
createdAt
}
}
}
}
}
`,
"variables": {
"first": 3,
"after": "Yzp7ImNyZWF0ZWRBdCI6IjIwMTYtMTItMDhUMTU6MDI6MzguMDAwWiIsImlkIjo2fQ==",
"orderBy": {
"field": "createdAt",
"direction": "DESC"
}
}
};
request({
url: "https://us-west-2.api.scaphold.io/graphql/scaphold-graphql",
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: data
}, (error, response, body) => {
if (!error && response.statusCode == 200) {
console.log(JSON.stringify(body, null, 2));
} else {
console.log(error);
console.log(response.statusCode);
}
});
The above command returns an object structured like this:
{
"data": {
"viewer": {
"allUsers": {
"edges": [
{
"cursor": "Yzp7ImNyZWF0ZWRBdCI6IjIwMTYtMTItMDhUMTU6MDI6MjMuMDAwWiIsImlkIjo1fQ==",
"node": {
"username": "Steve Jobs",
"createdAt": "2016-12-08T15:02:23.000Z"
}
},
{
"cursor": "Yzp7ImNyZWF0ZWRBdCI6IjIwMTYtMTItMDhUMTU6MDI6MTcuMDAwWiIsImlkIjo0fQ==",
"node": {
"username": "Bill Gates",
"createdAt": "2016-12-08T15:02:17.000Z"
}
},
{
"cursor": "Yzp7ImNyZWF0ZWRBdCI6IjIwMTYtMTItMDhUMTU6MDI6MDYuMDAwWiIsImlkIjozfQ==",
"node": {
"username": "Larry Page",
"createdAt": "2016-12-08T15:02:06.000Z"
}
}
]
}
}
}
}
The viewer is a convention from Relay
that allows you to both easily paginate
through all objects in your app as well as get a view for the currently logged in user. We generate a viewer field
allX
for each type X
in your schema. Queries that go through the viewer are subject to your set permissions so
users can only access the data you allow.
The viewer also contains a user
field that will always return information on the currently logged in user.
It’s powerful since it uses connections and cursors to enable pagination out of the box! Cursors are essentially
a mechanism that uniquely identifies a single piece of data in a paginated list. With this unique ID, you can
apply it to the after
or before
parameters in your query to retrieve data past or prior to a particular piece
of data, respectively.
Mutations
A GraphQL mutation is a write followed by a fetch in one operation.
Mutations are your means of modifying data in your API. Each core data type X
, gets a createX
, updateX
, and deleteX
mutation. Input arguments are automatically created to fit your schema and can be inspected from GraphiQL’s Doc Explorer.
Here’s an example of each type of mutation:
Create
curl -X POST https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
-H "Content-Type: application/json" \
-d '{ "query": "mutation CreateUser($user: CreateUserInput!) { createUser(input: $user) { changedUser { id username } } }",
"variables": { "user": { "username"<EMAIL_ADDRESS>"password": "SuperSecretPassword" } } }'
import request from 'request';
const data = {
"query": `
mutation CreateUser($user: CreateUserInput!) {
createUser(input: $user) {
changedUser {
id
username
}
}
}
`,
"variables": {
"user": {
"username"<EMAIL_ADDRESS>"password": "SuperSecretPassword"
}
}
};
request({
url: "https://us-west-2.api.scaphold.io/graphql/scaphold-graphql",
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: data
}, (error, response, body) => {
if (!error && response.statusCode == 200) {
console.log(JSON.stringify(body, null, 2));
} else {
console.log(error);
console.log(response.statusCode);
}
});
The above command returns an object structured like this:
{
"data": {
"createUser": {
"changedUser": {
"id": "VXNlcjo3",
"username"<EMAIL_ADDRESS>}
}
}
}
In this request, JSON-formatted variables are used to send an object as part of the payload for the mutation request.
The dollar sign ($) specifies a GraphQL variable, and the variable definition can be found in the variables section
of the request. Another thing to note here is that when introducing variables in the mutation string,
$user: CreateUserInput!
means that that variable is of type CreateUserInput!
and is required (!).
Update
curl -X POST https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
-H "Content-Type: application/json" \
-d '{ "query": "mutation UpdateUser($user: UpdateUserInput!) { updateUser(input: $user) { changedUser { id username biography } } }",
"variables": { "user": { "id": "VXNlcjox", "biography": "Spends his days saving the world with renewable energy." } } }'
import request from 'request';
const data = {
"query": `
mutation UpdateUser($user: UpdateUserInput!) {
updateUser(input: $user) {
changedUser {
id
username
biography
}
}
}
`,
"variables": {
"user": {
"id": "VXNlcjox",
"biography": "Spends his days saving the world with renewable energy."
}
}
};
request({
url: "https://us-west-2.api.scaphold.io/graphql/scaphold-graphql",
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: data
}, (error, response, body) => {
if (!error && response.statusCode == 200) {
console.log(JSON.stringify(body, null, 2));
} else {
console.log(error);
console.log(response.statusCode);
}
});
The above command returns an object structured like this:
{
"data": {
"updateUser": {
"changedUser": {
"id": "VXNlcjox",
"username": "Elon Musk",
"biography": "Spends his days saving the world with renewable energy."
}
}
}
}
You can also make a mutation to update data. The update operation performs a non-destructive update
to an object in your dataset. I.E. Update only updates the fields that you include as part of the
input. The object’s id
is required in every update mutation so that we can uniquely identify the
object you would like to update. If you don’t know it, you should perform a query operation to fetch
the data first, or save it in your application’s state after creating the object.
Delete
curl -X POST https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
-H "Content-Type: application/json" \
-d '{ "query": "mutation DeleteUser($user: DeleteUserInput!) { deleteUser(input: $user) { changedUser { id username } } }",
"variables": { "user": { "id": "VXNlcjo4" } } }'
import request from 'request';
const data = {
"query": `
mutation DeleteUser($user: DeleteUserInput!) {
deleteUser(input: $user) {
changedUser {
id
username
}
}
}
`,
"variables": {
"user": {
"id": "VXNlcjo4"
}
}
};
request({
url: "https://us-west-2.api.scaphold.io/graphql/scaphold-graphql",
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: data
}, (error, response, body) => {
if (!error && response.statusCode == 200) {
console.log(JSON.stringify(body, null, 2));
} else {
console.log(error);
console.log(response.statusCode);
}
});
The above command returns an object structured like this:
{
"data": {
"deleteUser": {
"changedUser": {
"id": "VXNlcjo4",
"username"<EMAIL_ADDRESS>}
}
}
}
Use delete operations to delete data from your API. Delete requires the unique global identifier id
of the piece of data that you wish to delete. Upon deleting data, you will receive the data back one
last time in case you need it again, and to serve as confirmation that that particular object was removed.
Subscriptions
Example: Subscribe and get a real-time feed of when any user logs in or is created.
Query
subscription SubscribeToUser($user: [UserMutationEvent]!) {
subscribeToUser(mutations: $user) {
mutation
value {
id
username
}
}
}
Variables
{
"user": [
"loginUser",
"createUser"
]
}
When Facebook open-sourced GraphQL, they described how applications can perform reads with queries, and writes with mutations. However, oftentimes clients want to get pushed updates from the server when data they care about changes. Enter Subscriptions. Subscriptions make real-time functionality a first class citizen in GraphQL!
Subscriptions offer a clean and efficient way to get pushed updates in real-time. They act in parallel to mutations. Just like how mutations describe the set of actions you can take to change your data, subscriptions define the set of events that you can subscribe to when data changes. In fact, you can think of subscriptions as a way to react to mutations made elsewhere.
For example, think about a chat application like Slack. To create a good user experience, our application needs to stay up to date at all times. I.E. when a co-worker sends me a message, I shouldn’t have to refresh the page to see the message. A much better solution is to have the server push my chat client the message as soon as it is created. This is how subscriptions work. When someone creates a message (or in other words issues a mutation), the server immediately pushes the data to every client that is both subscribed to that event.
Subscriptions in GraphiQL
Query
subscription SubscribeToNewMessages($messageFilter:MessageSubscriptionFilter) {
subscribeToMessage(mutations:[createMessage], filter:$messageFilter) {
mutation
value {
id
content
channel {
id
name
}
createdAt
}
}
}
Variables
{
messageFilter: {
content: {
matches: ".\*GraphQL.\*"
},
channelId: {
eq: "SavedChannelId"
}
}
}
You can play around with Subscriptions in our GraphiQL page! It’s hooked up to handle web socket connections, so Subscription requests will work immediately. Normal HTTP clients won’t work with Subscriptions since it requires a web socket connection.
This is what I’m doing:
1) I create a Channel
object with name GraphQL News!
and save its id.
2) By issuing the query to the right, I subscribe to all new messages that are created in the GraphQL News!
channel
and that have content matching the regex .*GraphQL.*
3) I send a Message
with content GraphQL is future!
to channel GraphQLNews!
and see a message pushed into the Subscription stream.
4) I send a Message
with content REST is dead!
to channel GraphQLNews!
and no message appears in the Subscription stream.
5) I send a Message
with content GraphQL Subscriptions are Awesome!
to channel GraphQLNews!
and another message appears in the Subscription stream.
6) I close the Subscription by double-tapping the pulse icon in the subscription stream.
Subscribing to changes to data in your API is that easy! There are a number of subscription filters you can use to fine tune your subscriptions and you can even filter on One-To-Many and One-to-One connections using the associated id filters.
Our Subscription implementation works (almost) out of the box with Apollo Client. Follow these steps to make your app real-time!
Resources
Connections & Pagination
Each type will have associated
Connection
types like so:
type XConnection {
edges: [XEdge]
pageInfo: PageInfo
}
type XEdge {
cursor: String
node: X
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}
All connection fields will take the form:
query {
connectionFieldOfTypeX (
first: Int,
after: String!,
last: Int,
before: String,
orderBy: String
) {
edges {
cursor
node {
...fields in type X
}
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}
We use the concept of Connections
to provide a standardized way of paginating through large sets of objects.
You can use the Connection
type when defining your schema whenever you need to model relations on potentially
large sets of data.
Connections expose the following arguments:
| Name | Type | Description |
|---|---|---|
| where | Object |
Object that represents SQL-like terms used for filtering on a per-field basis |
| orderBy | List |
Fields that you wish to order by, and the order in which your app needs the data (ASC or DESC ) |
| first | Int |
Acts as a limiting number of records to return and counts forward (i.e. from 0 to n) |
| after | String |
Pass in the cursor of an object, and you will retrieve the data after that particular record in a paginated list |
| last | Int |
Acts as a limiting number of records to return and counts backward (i.e. from n to 0) |
| before | String |
Pass in the cursor of an object, and you will retrieve the data before that particular record in a paginated list |
One-to-One
curl -X POST https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
-H "Content-Type: application/json" \
-d '{ "query": "mutation UpdateCountry($input: UpdateCountryInput!) { updateCountry(input: $input) { changedCountry { id name capitalCity { id name } } } }",
"variables": { "input": { "id": "Q291bnRyeTox", "capitalCityId": "Q2FwaXRhbENpdHk6MQ==" } } }'
var request = require('request');
var data = {
"query": `
mutation UpdateCountry($input: UpdateCountryInput!) {
updateCountry(input: $input) {
changedCountry {
id
name
capitalCity {
id
name
}
}
}
}
`,
"variables": {
"input": {
"id": "Q291bnRyeTox", // Country ID
"capitalCityId": "Q2FwaXRhbENpdHk6MQ==" // Capital City ID
}
}
}
request({
url: "https://us-west-2.api.scaphold.io/graphql/scaphold-graphql",
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: data
}, function(error, response, body) {
if (!error && response.statusCode == 200) {
console.log(JSON.stringify(body, null, 2));
} else {
console.log(error);
console.log(response.statusCode);
}
});
The above command returns an object structured like this:
{
"data": {
"updateCountry": {
"changedCountry": {
"id": "Q291bnRyeTox",
"name": "United States",
"capitalCity": {
"id": "Q2FwaXRhbENpdHk6MQ==",
"name": "Washington, D.C."
}
}
}
}
}
Given two types, Country
and CapitalCity
, you can designate a one-to-one relationship between them since one country can only have one capital city. In a one-to-one connection
between two types, there will be a field on each of the connected types that refers back to the other (i.e. reverse name). With that, you can associate an instance of Country
with another instance of CapitalCity
.
Steps:
- Provided the two types have been created already, add a field called
country
toCapitalCity
with this configuration.
Create an instance of
Country
.Create an instance of
CapitalCity
.Update the country instance with the id of the newly created capital city on the
capitalCityId
field.
One-to-Many
curl -X POST https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
-H "Content-Type: application/json" \
-d '{ "query": "mutation UpdateFile($input: UpdateFileInput!) { updateFile(input: $input) { changedFile { id name owner { id username } } } }",
"variables": { "input": { "id": "RmlsZTox", "ownerId": "VXNlcjox" } } }'
var request = require('request');
var data = {
"query": `
mutation UpdateFile($input: UpdateFileInput!) {
updateFile(input: $input) {
changedFile {
id
name
owner {
id
username
}
}
}
}
`,
"variables": {
"input": {
"id": "RmlsZTox", // File ID
"capitalCityId": "VXNlcjox" // User ID
}
}
}
request({
url: "https://us-west-2.api.scaphold.io/graphql/scaphold-graphql",
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: data
}, function(error, response, body) {
if (!error && response.statusCode == 200) {
console.log(JSON.stringify(body, null, 2));
} else {
console.log(error);
console.log(response.statusCode);
}
});
The above command returns an object structured like this:
{
"data": {
"updateFile": {
"changedFile": {
"id": "RmlsZTox",
"name": "profilePicture",
"owner": {
"id": "VXNlcjox",
"username": "Elon Musk"
}
}
}
}
}
One-to-many relationships work very similarly to one-to-one relationships. The difference is that after you connect two types, you must update the instance on the “many side” of the connection. Only the “many side” instance will have a field that associates it to an instance of the “one side” of the relationship.
Given two types, User
and File
, you can designate a one-to-many relationship between them since one user can have multiple files, while a file can only have one owner (user). In a
one-to-many connection between two types, there will be a field injected on the file type called ownerId (id of the user). With that, you can associate an instance of User
with another instance of File
.
Steps:
- Provided the two types have been created already, add a field called
files
toUser
with this configuration.
Create an instance of
User
.Create an instance of
File
.Update the file instance with the id of the newly created user on the
ownerId
field.
Many-to-Many
For example, suppose we have the following schema:
type User {
id: ID!
username: String!
posts: [Post]
edits: [Post]
}
type Post {
id: ID!
title: String!
content: String
author: User
editors: [User]
}
Many-to-many connections are defined by having two types with Connection fields pointing to one another via their reverseName
and have a special workflow for managing edges between their objects.
Given the example on the right, Scaphold will generate 3 special mutations for dealing with the many-to-many relations between User and Post via the edits & editors fields. These mutations will be:
addToUserEditsConnection
updateUserEditsConnection
removeFromUserEditsConnection
These mutations can be used to add edges between objects in the many-to-many connection.
Special Case
Special Case Example
curl -X POST https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
-H "Content-Type: application/json" \
-d '{ "query": "mutation AddFriendship($input: AddToFriendshipConnectionInput!) { addToFriendshipConnection(input: $input) { changedFriendship { status user1 { id username } user2 { id username } } } }",
"variables": { "input": { "user1Id": "VXNlcjo0", "user2Id": "VXNlcjoz", "status": "Accepted" } } }'
var request = require('request');
var data = {
"query": `
mutation AddFriendship($input: AddToFriendshipConnectionInput!) {
addToFriendshipConnection(input: $input) {
changedFriendship {
status
user1 {
id
username
}
user2 {
id
username
}
}
}
}
`,
"variables": {
"input": {
"user1Id": "VXNlcjo0",
"user2Id": "VXNlcjoz",
"status": "Accepted"
}
}
}
request({
url: "https://us-west-2.api.scaphold.io/graphql/scaphold-graphql",
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: data
}, function(error, response, body) {
if (!error && response.statusCode == 200) {
console.log(JSON.stringify(body, null, 2));
} else {
console.log(error);
console.log(response.statusCode);
}
});
The above command returns an object structured like this:
{
"data": {
"addToFriendshipConnection": {
"changedFriendship": {
"status": "Accepted",
"user1": {
"id": "VXNlcjo0",
"username": "Steve Jobs"
},
"user2": {
"id": "VXNlcjoz",
"username": "Bill Gates"
}
}
}
}
}
If you wanted to create a many-to-many relationship between a type and itself, Scaphold makes it easy to do so! Perhaps you wanted to create a relation between a User type and itself, thereby defining a friendship.
The steps to do so are:
Add a field to User called
friends
.It is a
Connection
of typeUser
with a many-to-many relationship and a reverse name that is the same as the field name. Upon selecting the many-to-many relationship, a new field will appear calledConnection Name
. This will be the name of the “join table” (in the SQL sense) that will be automatically generated for you after creating the new connection.The resulting “join table” will be a new type in your schema. This creates a new table in Scaphold that will hold all your data that pertains to the edges of the connection for friends on the User type and you can add additional fields to this type as well.
Filtering (WhereArgs)
curl -X POST https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
-H "Content-Type: application/json" \
-d '{ "query": "query ($where: UserWhereArgs) { viewer { allUsers(where: $where) { edges { node { id username } } } } }",
"variables": { "where": { "username": { "like": "%elon%" } } } }'
var request = require('request');
var data = {
"query": `
query ($where: UserWhereArgs) {
viewer {
allUsers(where: $where) {
edges {
node {
id
username
}
}
}
}
}
`,
"variables": {
"where": {
"username": {
"like": "%elon%"
}
}
}
}
request({
url: "https://us-west-2.api.scaphold.io/graphql/scaphold-graphql",
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: data
}, function(error, response, body) {
if (!error && response.statusCode == 200) {
console.log(JSON.stringify(body, null, 2));
} else {
console.log(error);
console.log(response.statusCode);
}
});
The above command returns an object structured like this:
{
"data": {
"viewer": {
"allUsers": {
"edges": [
{
"node": {
"id": "VXNlcjo3",
"username"<EMAIL_ADDRESS>}
},
{
"node": {
"id": "VXNlcjox",
"username": "Elon Musk"
}
}
]
}
}
}
}
Scaphold allows you to write GraphQL queries that are compiled down to SQL which means you get powerful filtering abilities with WhereArgs
as well as compound orderBy
expressions. We even expose the ability for you to
index certain fields in your data so that you can optimize the queries that are important to your application.
For each Node-implemented type, we provide a way to query using SQL-like syntax through GraphQL! For each field on a particular type, you are able to query by SQL operators and you will be returned a list of instances that satisfy those filter arguments. The allowable operators include the following:
| Name | Description |
|---|---|
| eq | Equal to. This takes a higher precedence than the other operators. |
| gt | Greater than. |
| gte | Greater than or equal to. |
| lt | Less than. |
| lte | Less than or equal to. |
| ne | Not equal to. |
| between | A two element tuple describing a range of values. |
| notBetween | A two element tuple describing an excluded range of values. |
| in | A list of values to include. |
| notIn | A list of values to exclude. |
| like | A pattern to match for likeness. |
| notLike | A pattern to match for likeness and exclude. |
| isNull | Filters for null values. This takes precedence after eq but before all other fields. |
Authentication
Scaphold seemelessly handles user authentication for you. Each Scaphold application comes with a default User model which includes a username
and password
that are used to authenticate your users.
We securely encrypt and store each user’s password as well as ensure that they are not readable.
Token
Example
loginUser
query
curl -X POST https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
-H "Content-Type: application/json" \
-d '{"query": "mutation LoginUserQuery ($input: LoginUserInput!) { loginUser(input: $input) { token user { id username createdAt } } }",
"variables": { "input": { "username"<EMAIL_ADDRESS>"password": "SuperSecretPassword" } } }'
import request from 'request';
const data = {
"query": `mutation LoginUserQuery ($input: LoginUserInput!) {
loginUser(input: $input) {
token
user {
id
username
createdAt
}
}
}`,
"variables": {
"input": {
"username"<EMAIL_ADDRESS>"password": "SuperSecretPassword"
}
}
};
request({
url: "https://us-west-2.api.scaphold.io/graphql/scaphold-graphql",
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: data
}, (error, response, body) => {
if (!error && response.statusCode == 200) {
console.log(JSON.stringify(body, null, 2));
} else {
console.log(error);
console.log(response.statusCode);
}
});
The above command returns an object structured like this:
{
"data": {
"loginUser": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODI4ODI0ODgsImlhdCI6MTQ4MTU4NjQ4OCwiYXVkIjoiNDRiZTA4NmYtYmYzMy00OTk3LTgxMzYtOWMwMWQ5OWE4OGM0IiwiaXNzIjoiaHR0cHM6Ly9zY2FwaG9sZC5pbyIsInN1YiI6IjcifQ.TDRtD5vD7MIVrViDgVMThhzOzE_teufTo51a4GZ3aGA",
"user": {
"id": "VXNlcjo3",
"username"<EMAIL_ADDRESS>"createdAt": "2016-12-08T20:43:14.000Z"
}
}
}
}
Important: Use the
token
in the response in the header of future requests as:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODI4ODI0ODgsImlhdCI6MTQ4MTU4NjQ4OCwiYXVkIjoiNDRiZTA4NmYtYmYzMy00OTk3LTgxMzYtOWMwMWQ5OWE4OGM0IiwiaXNzIjoiaHR0cHM6Ly9zY2FwaG9sZC5pbyIsInN1YiI6IjcifQ.TDRtD5vD7MIVrViDgVMThhzOzE_teufTo51a4GZ3aGA
Logging in a user is simple. Use the loginUser
mutation we provide you and we will return a JSON Web Token (JWT) if the credentials match. To authenticate a user, you simply set the Authorization
HTTP header of your request with the format Bearer {TOKEN_FROM_LOGIN_USER}
.
This token informs your API what user is logged in at any given time and enables our permissions system to layer access control rules on your data.
Permissions (Authorization)
Important: If a type has no permissions then anyone can perform any operations on it so make sure you add them before launching!
Scaphold implements a permissions system that allows you to define powerful access control rules by leveraging a combination of features from role-based access control systems (RBAC) as well as the connections in your API’s graph to define what users can access what information.
Each type and field in your schema has an optional set of permissions applied to it. When a user tries to complete an operation, your API checks the type- and field-level permissions and validates that the user is authorized to complete that operation. When you login to the Scaphold portal, you are logged in as an admin user and will have complete access to your application.
To add a permission, click on the +
sign in the top right for the panel of the type you wish to add a permission to. There you can add a permission for all of the fields of a particular type or only specific fields on that type.
The following sections delineate the various types of permissioning that you can apply to your data.
Everyone
Everyone
scoped permissions are the loosest available. Everyone can access the type. No login required.
Authenticated
Authenticated
scoped permissions required a valid auth token to be present in the request headers (See authentication for more details). Scaphold provides a number of authentication mechanisms that all work seamlessly with your permissions.
Roles
GraphQL types used in role-based permissioning to manage roles, members, and access levels.
type User {
id: ID!
username: String
password: Secret
credentials: CredentialConnection
lastLogin: DateTime
roles: RoleConnection
createdAt: DateTime
modifiedAt: DateTime
}
type Role {
id: ID
name: String
members: UserConnection
createdAt: DateTime
modifiedAt: DateTime
}
type UserRoles {
accessLevel: AccessLevel
createdAt: DateTime
modifiedAt: DateTime
}
enum AccessLevel {
admin
readwrite
readonly
}
Role
permissions allow you to layer more generic role-based authentication methods on top of the connection and user-based permissions you already have. We have added a few queries and mutations that make it easy to manage your new roles and permissions.
In particular, you role-based permissioning is concerned with 3 object types: User
, Role
, and a through type called UserRoles
. In addition, we use the enum AcessLevel
to manage how much access we should provide a particular instance of a UserRole
.
| Name | Inputs | Description |
|---|---|---|
| getRole | id: ID! |
Get objects of type Role by id. |
| createRole | name: String! |
Creates a new role and enrolls the creator as a role admin. |
| updateRole | id: ID! , name: String |
Update an existing role. |
| deleteRole | id: ID! |
Delete an existing role. Only role admins can delete roles. |
| addToUserRolesConnection | userId: ID! , roleID: ID! , accessLevel: AccessLevel |
Enrolls a user as a member of a role. Only admins can create other admins and you must be an admin to enroll another user. |
| updateUserRolesConnection | userId: ID! , roleID: ID! , accessLevel: AccessLevel |
Updates a connection between an object of type User and an object of type Role . |
| removeFromUserRolesConnection | userId: ID! , roleId: ID! |
Removes a user from a role. Anyone can disenroll themself and admins can disenroll anyone. |
By default, we create a special admin
role for each of your apps. Users that are enrolled into the admin
role are given full access to your GraphQL API without having to specify any custom permissions.
Role
scoped permissions can be used alongside existingRelation
scoped permissions to easily create complex access control rules. Let’s take an example where we would like to have a set of notes that only executives of my company can see. Part of our schema might look something like this:
// Schema
type User {
id: ID!
username: String!
authoredExecNotes: ExecutiveNoteConnection
}
type ExecutiveNote {
id: ID!
author: User
content: String
}
// Permissions
[{
scope: "ROLE",
roles: ["Executives"],
read: true,
create: true
}
{
scope: "RELATION",
userFields: ["author"],
update: true,
delete: true
}]
Relation
Example of
Relation
permissionA
Post
model might have anauthor
. You could specify that only the author of the post can update it by adding the following permission to thePost
type:
{
scope: "RELATION",
userFields: ["author"],
update: true
}
Relation
scoped permissions use the connections in your data’s graph to authorize behaviors. When you add a Relation
permission you tell Scaphold what User Fields
to inspect to check for the logged in user.
There is a special Relation
scope permission that gives a user sole access to their own user object. To enable this behavior create a Relation
scope permission with the desired operation and the user field me
.
A common use case for the me
user field is to give a user sole access to update their own user object.
Social
For social authentication, Scaphold has a solution for that as well! Integrating with OAuth providers like Facebook, Google, and Twtiter has never been easier with Scaphold’s Auth0 integration.
Find out how to integrate social authentication or integrate passwordless authentication.
Super Users
Scaphold provides the ability for you to generate an admin token that gives you super user access that supercedes all permission rules and access controls. This is a great tool for importing or exporting data, any one-off data management tasks that need to be done, or scheduled jobs.
Please refer to the Admin Token section under App Management for more details.
Real-Time
Subscriptions With Apollo Client
Condensed example from our Slackr app (JavaScript only)
/* File: addGraphQLSubscriptions.js */
import { print } from 'graphql-tag/printer';
// quick way to add the subscribe and unsubscribe functions to the network interface
export default function addGraphQLSubscriptions(networkInterface, wsClient) {
return Object.assign(networkInterface, {
subscribe(request, handler) {
return wsClient.subscribe({
query: print(request.query),
variables: request.variables,
}, handler);
},
unsubscribe(id) {
wsClient.unsubscribe(id);
},
});
}
/* End of file: addGraphQLSubscriptions.js */
------------------------------------------------------------------------
/* File: makeApolloClient.js */
import addGraphQLSubscriptions from './addGraphQLSubscriptions';
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import { Client } from 'subscriptions-transport-ws';
// creates a subscription ready Apollo Client instance
export function makeApolloClient() {
const scapholdUrl = 'us-west-2.api.scaphold.io/graphql/scaphold-graphql';
const graphqlUrl = `https://${scapholdUrl}`;
const websocketUrl = `wss://${scapholdUrl}`;
const networkInterface = createNetworkInterface(graphqlUrl);
networkInterface.use([{
applyMiddleware(req, next) {
// Easy way to add authorization headers for every request
if (!req.options.headers) {
req.options.headers = {}; // Create the header object if needed.
}
if (localStorage.getItem('scaphold_user_token')) {
// This is how to authorize users using http auth headers
req.options.headers.Authorization = `Bearer ${localStorage.getItem('scaphold_user_token')}`;
}
next();
},
}]);
const wsClient = new Client(websocketUrl);
const networkInterfaceWithSubscriptions = addGraphQLSubscriptions(networkInterface, wsClient);
const clientGraphql = new ApolloClient({
networkInterface: networkInterfaceWithSubscriptions,
initialState: {},
});
return clientGraphql;
}
/* End of File: makeApolloClient.js */
Apollo Client is a (currently) JavaScript-only networking interface that provides a fully-featured caching GraphQL client for any server or UI framework. It’s an easy-to-use GraphQL networking client that works with HTTP and web socket requests. We use it at Scaphold to power our web apps, and many of the examples in our GitHub. But you can also use it for your React Native apps as well to address mobile needs. They are coming out with support for native mobile iOS and Android clients as well. You can read more about it here.
The Scaphold GraphiQL page has already implemented the subscription protocol for you. The good news is that it is really easy to set this up in your own application. Here is how.
1) Download Apollo Client from npm! (Apollo Client works pretty much the same whether you are building a React, AngularJS, or vanilla JavaScript applications)
npm install apollo-client graphql-tag --save
If using React also
npm install react-apollo --save
If using Angular2 also
npm install angular2-apollo --save
2) Configure the Apollo Client network layer to work with websockets. To do this we can use the following two code snippets:
The addGraphQLSubscriptions
function retrofits the Apollo Client network interface with the subscribe and unsubscribe methods that we can use from our application code.
The makeApolloClient
function then creates a new Apollo Client instance, applies the subscription methods, and adds a peice of authentication middleware before returning the client for use in our application.
This is all we need to do to configure our Apollo Client instance for GraphQL Subscriptions.
Subscriptions With React
Condensed example from our Slackr app (JavaScript only)
import React from 'react';
import { graphql, compose } from 'react-apollo';
import gql from 'graphql-tag';
const ChannelMessagesQuery = gql`
query GetPublicChannels($channelId: ID!, $messageOrder: [MessageOrderByArgs]) {
getChannel(id: $channelId) {
id
name
messages(last: 50, orderBy: $messageOrder) {
edges {
node {
id
content
createdAt
author {
id
username
nickname
picture
}
}
}
}
}
}
`;
class Messages extends React.Component {
...
componentWillReceiveProps(newProps) {
if (
!newProps.data.loading &&
newProps.data.getChannel
) {
if (
!this.props.data.getChannel ||
newProps.data.getChannel.id !== this.props.data.getChannel.id
) {
// If we change channels, subscribe to the new channel
this.subscribeToNewMessages();
}
}
}
/*
* Initiates the subscription and specifies how new data should be merged
* into the cache using the updateQuery method.
*/
subscribeToNewMessages() {
this.subscription = this.props.data.subscribeToMore({
document: gql`
subscription newMessages($subscriptionFilter:MessageSubscriptionFilter) {
subscribeToMessage(mutations:[createMessage], filter: $subscriptionFilter) {
value {
id
content
createdAt
author {
id
username
nickname
picture
}
}
}
}
`,
variables: {
subscriptionFilter: {
channelId: {
// We're using react-router and grabbing the channelId from the url
// to designate which channel to subscribe to
eq: this.props.params ? this.props.params.channelId : null
}
}
},
/*
* Update query specifies how the new data should be merged
* with our previous results. Note how the structure of the
* object we return here directly matches the structure of
* the GetPublicChannels query.
*/
updateQuery: (prev, { subscriptionData }) => {
const newEdges = [
...prev.getChannel.messages.edges,
{
node: {
...subscriptionData.data.subscribeToMessage.value,
}
}
];
return {
getChannel: {
messages: {
edges: newEdges,
}
}
};
},
});
}
...
}
const MessagesWithData = compose(
graphql(ChannelMessagesQuery, {
options: (props) => {
const channelId = props.params ? props.params.channelId : null;
return {
returnPartialData: true,
variables: {
channelId,
messageOrder: [
{
field: 'createdAt',
direction: 'ASC'
}
],
},
};
},
}),
... // We compose a few more queries in the actual app.
)(Messages);
export default MessagesWithData;
Let’s look at a more real world example using React. Apollo comes packed with really nice React bindings that we can use to simplify the process of subscribing to data and merging new data into the client-side cache.
Take a minute to look over this file. Apollo does a great job allowing us to use real-time subscriptions alongside traditional queries and mutations. The combination of subscribeToMore
and updateQuery
offer a powerful set of tools to keep our UI up to date when dealing with real-time data!
See the complete code on GitHub
The returnPartialData: true
option is important. When you want to use subscribeToMore
to merge results into the result of a normal query it is necessary to specify this.
A few things are going on here. To make sense of what is happening, lets start from the logical beginning which actually occurs at the end of the file. See this line const MessagesWithData = compose(graphql(ChannelMessagesQuery, ...));
. This is the standard way to use Apollo to connect a react component with data from a GraphQL query. The graphql
function will wrap our component in a higher-order component that grabs our data and makes it available to our component via its props
. In this example, we will be able to access our Channel
data from our component with this.props.data.getChannel
as soon as it is fetched.
Okay so we have connected our component with a regular old GraphQL query, but how do we make it real-time? The key is the method subscribeToMore
. Look at our subscribeToNewMessages
method. Apollo’s graphql
function fits our component with the data prop that exposes the subscribeToMore
method. We use this method to attach a subscription query which then calls the updateQuery
method we pass in every time a new peice of data is pushed from the server. The object we return from updateQuery
is then merged with our previous results and persisted in the client side cache. This way, this.props.data.getChannel...
is always kept up to date and we can use it like normal to render our UI.
The Full Slackr Tutorial
Follow along as we build a real-time chat application called Slackr. We will cover subscriptions in depth and show you how to quickly make real-time apps with Apollo Client It covers a lot of material and has a full example app to boot!
How to Build real-time Apps with GraphQL Subscriptions
Slackr Starter Kit
Check out the Slackr Starter Kit on Github. Use it to jump start your next real-time application!
Integrations
Use Scaphold’s integration infrastructure to tie third-party services into your API. When you add an integration, we will automatically update your API with new functionality. Integrations range in purpose. Want notifications? Add iOS Push. Want payments? Integrate Stripe. Need social auth? Add it with Auth0. Our collection of integrations is always expanding so please let us know if you have any specific requests.
Integrations come in many shapes and sizes. Some integrations like Slack and Webhooks are powered by events in your API while others like Mailgun will expose their functionality by adding queries and mutations to your GraphQL API.
We’ve got some exciting new integrations coming out soon! Please let us know if you have any specific requests.
Use Mailgun to send and manage email from within your application. Bind emails to events to automate your workflow.
- Create a free Mailgun account.
- Add the Mailgun integration and enter your API key and domain name in Scaphold’s Integrations Portal.
- Start sending emails and managing mailing lists from your Scaphold API!
Monitoring
Use Apollo Optics to add in-depth analytics for your API to understand how to optimize your queries.
- Create an Apollo Optics account.
- Add the Apollo Optics integration and enter your API key in Scaphold’s Integration Portal.
Every request you make will send information about how your data is being fetche, so you can view the performance of your all your requests through the comprehensive Optics dashboard.
Passwordless Auth
We’ve built a little playground for you to test out passwordless auth using Twitter Digits in an iOS app. View on GitHub
Please reference this example if you’re unfamiliar with how to obtain OAuth Echo Headers: Sample Code
Use Twitter Digits to extend your app’s authentication capabilities, and log in your users with passwordless authentication using their phone number.
Create a free Twitter Digits account online using Fabric. This is the account management platform that Twitter uses to manage your Twitter integrations for your apps.
Once you’ve done so and installed Digits to your app, follow the steps on the Digits Sandbox to configure your native app to test out the functionality in debug mode.
When you have this set up, you’ll need to obtain OAuth Echo Headers:
Now you can make a request with these header values with the new mutation called
loginUserWithDigits
that will allow you to log in a user with this integration.It will return a JWT token that you need to include in your future requests as the bearer token for your Authorization header so that we can authenticate this particular logged in user.
This works across authentication mechanisms on Scaphold, so you can link any number of social or passwordless authentication flows.
Payments
Hook into Stripe to instantly add advanced payment methods to your application.
The Stripe integration allows you to quickly and easily link your company’s Stripe account with your Scaphold API.
- Head to Stripe and create a free account to get started.
- Upload your secret key and publishable key via our Integrations Portal.
- Rejoice as we have just added tons of new payments functionality to your API.
Push Notifications
Add iOS or Android push notifications in an instant.
- Follow this tutorial to create an SSL certificate and PEM file
- Add the iOS integration and upload your SSL cert and PEM file through our Integrations Portal.
- Immediately get access to iOS or Android push notifications via your app’s GraphQL API.
Once you’ve set up your keys, you can start managing device tokens across platforms and send push notification messages to valid device tokens and users.
Search
We’ve built a small boilerplate for you to get started quickly with Auth0 Lock in React.
Use Algolia to extend your app’s search capabilities, and query your data seamlessly as your data changes.
Create a free Algolia account. This will help you manage your search indices and monitor your usage. Once you’ve created your account, you’ll receive an Application ID and an API Key.
Configure Algolia in Scaphold from the Integrations Portal and select the checkboxes in the popup to index data of that particular type.
As soon as you enable search on a particular type, Scaphold will index each existing piece of data for that type into Algolia’s system and future CREATE, UPDATE, and DELETE commands will be reflected in both Scaphold and Algolia for search. These commands will be the same as before; however you now have a new query for each indexed type under Viewer that will allow you to search for query terms along with optional parameters.
Social Auth
Use Auth0 to extend your app’s login and registration flow painlessly through your favorite social authentication services.
Create a free Auth0 account. This will help you manage your app credentials like client IDs and secrets for your OAuth providers. By connecting your apps on your social accounts like Facebook, Google, and Twitter, you’ll then have the correct account credentials to utilize these services for your authentication flow.
Configure Auth0 in Scaphold from the Integrations Portal to include the OAuth providers that you plan to use for your app. This will enable a new mutation called
loginUserWithAuth0Social
which you can use to log in users with connected OAuth providers.In your client app, you’ll likely be using a client SDK to handle user login. For instance, you can use the Facebook SDK for React Native to ask your users to log in with their existing Facebook accounts. Once this succeeds, you’ll receive an
access token
to send to Scaphold.After Scaphold verifies the access token with the OAuth provider (i.e. Facebook), we’ll pass back the JSON Web Token (JWT) that you’ll need to add to your authorization header for future requests. That way, you will be authorized to make future requests through Scaphold and it will also provide us the capability to work on that user’s behalf to access the OAuth provider’s resources. For instance, we could authorize you to access your Facebook friends and their public profile information.
In addition, if you’ve logged in already and you make a request to
loginUserWithAuth0Social
again but with a new OAuth provider (i.e. Google), Scaphold will link your two accounts together, since we know the requests being made belong to one user. Now, you’ll have access to both Facebook and Google information using Facebook’s and Google’s account credentials.If you’d like to use Auth0 Lock as a client-side SDK to manage your authentication, you can do so with the
loginUserWithAuth0Lock
mutation. This accepts the identity parameter that the client receives from a successful Auth0 Lock login in the profile variable.
Webhooks
Use the Custom integration to define webhooks, events, and custom logic in your API. Perhaps you would like to send an email to your users to welcome them after they’ve signed up. Webhooks are a powerful way to implement this workflow.
Host a microservice with an HTTP URL (you can use something like AWS Lambda) that defines the custom logic you need (perhaps to send a welcome email).
Configure the integration in Scaphold with the microservice URL that you want to send the request to, along with the fragment of the mutation that is associated with the data, in order to supply the microservice with the appropriate information.
Set when you want to the webhook to fire (e.g.
afterCreate
on theUser
type).
Files
File support in Scaphold is cooked into your schema. That means that any type that implements the Blob
interface can be associated with a file stored in our distributed blob storage. When you create
an app, we start you off with both the Blob
interface as well as a File
type. The File
type implements
Blob
and Node
and thus can both be connected to other types in your schema via Connection fields as well
as handle file uploads and downloads.
Uploading Files
Example file upload Mark Zuckerberg’s user ID:
VXNlcjoxMA==
curl -v https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
-H "Content-Type:multipart/form-data" \
-F 'query=mutation CreateFile($input: CreateFileInput!) { createFile(input: $input) { changedFile { id name blobMimeType blobUrl user { id username } } } }' \
-F 'variables={ "input": { "name": "Profile Picture", "userId": "VXNlcjoxMA==", "blobFieldName": "myBlobField" } };type=application/json' \
-F<EMAIL_ADDRESS>// You must also install these npm modules:
// npm install --save form-data
// npm install --save node-fetch
var fetch = require('node-fetch');
var FormData = require('form-data');
var fs = require('fs');
var form = new FormData();
form.append("query", `
mutation CreateFile($input: CreateFileInput!) {
createFile(input: $input) {
changedFile {
id
name
blobMimeType
blobUrl
user {
id
username
}
}
}
}
`);
form.append("variables", JSON.stringify({
"input": {
"name": "Mark Zuck Profile Picture",
"userId": "VXNlcjoxMA==",
"blobFieldName": "myBlobField"
}
}));
// The file's key matches the value of the field `blobFieldName` in the variables
form.append("myBlobField", fs.createReadStream('./mark-zuckerberg.jpg'));
fetch("https://us-west-2.api.scaphold.io/graphql/scaphold-graphql", {
method: 'POST',
body: form
}).then(function(res) {
return res.text();
}).then(function(body) {
console.log(body);
});
The above command returns an object structured like this:
{
"data": {
"createFile": {
"changedFile": {
"id": "RmlsZTo5",
"name": "Mark Zuck's Profile Picture",
"blobMimeType": "image/jpeg",
"blobUrl": "https://s3-us-west-2.amazonaws.com/production.us-west-2.scaphold.v2.customer/44be086f-bf33-4997-8136-9c01d99a88c4/data/2fb4b11d-cef9-465d-9ad6-e3d8b693f121/2b86488a-7114-4071-9e30-157855475eb7?AWSAccessKeyId=AKIAJIC3JY2ICINJH2OQ&Expires=1481686711&Signature=pa4QbkPCk%2BXlgSrKBWcRKsEckSs%3D",
"user": {
"id": "VXNlcjoxMA==",
"username": "Mark Zuckerberg"
}
}
}
}
}
Uploading files is simple. All you need to do is attach the file to a multipart/form-data request
and point to it using the blobFieldName
attribute in the Blob
implemented type’s input object.
All types that implement Blob
will receive an additional input field called blobFieldName
.
Querying Files
Example of querying for a file File ID:
RmlsZTo5
curl -X POST https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
-H "Content-Type: application/json" \
-d '{ "query": "query GetFile { getFile(id: \"RmlsZTo5\") { id name blobMimeType blobUrl user { id username } } }",
"variables": {} }'
var request = require('request');
var data = {
"query": `
query GetFile {
getFile(id: "RmlsZTo5") {
id
name
blobMimeType
blobUrl
user {
id
username
}
}
}
`,
"variables": {}
}
request({
url: "https://us-west-2.api.scaphold.io/graphql/scaphold-graphql",
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: data
}, function(error, response, body) {
if (!error && response.statusCode == 200) {
console.log(JSON.stringify(body, null, 2));
} else {
console.log(error);
console.log(response.statusCode);
}
});
The above command returns an object structured like this:
{
"data": {
"getFile": {
"id": "RmlsZTo5",
"name": "Mark Zuck's Profile Picture",
"blobMimeType": "image/jpeg",
"blobUrl": "https://s3-us-west-2.amazonaws.com/production.us-west-2.scaphold.v2.customer/44be086f-bf33-4997-8136-9c01d99a88c4/data/2fb4b11d-cef9-465d-9ad6-e3d8b693f121/2b86488a-7114-4071-9e30-157855475eb7?AWSAccessKeyId=AKIAJIC3JY2ICINJH2OQ&Expires=1481690209&Signature=icwTcNl%2B%2BTwQy8Ar6jLuquztwu0%3D",
"user": {
"id": "VXNlcjoxMA==",
"username": "Mark Zuckerberg"
}
}
}
}
Querying files acts the same as other types. All types that implement Blob
have a field blobUrl
and blobMimeType
that are automatically managed by Scaphold. The blobUrl
is a pre-signed URL that points to your file in a private
blob store hosted on Amazon S3. If youre app is on a paid tier, all pre-signed URLs will be served by
a globally distributed CDN hosted by Amazon CloudFront.
App Management
Aliases
Simplify your API URL with an alias!
Now when you create an app, you can choose to set an alias. An alias is a unique name for your application that can be used in the URL to access your API.
For example, if I set an alias for my app of scaphold
then I could access my application at the url https://us-west-2.api.scaphold.io/graphql/scaphold
.
You can also change an alias at any time by clicking My API
at the top of the page.
Analytics
Scaphold comes full circle with a comprehensive analytics dashboard so you can always get the latest updates on the performance, growth, and usage of your app in production.
| Name | Description |
|---|---|
| Request Counts | Total number of requests broken down by hour |
| Average Response Time | Measures latency (in seconds) in your API broken down by hour |
| User Growth | Tracks the number of new users that are signing up for your app |
| Data Throughput | Amount of data (in MB) that’s being transmitted over the wire to your client apps |
| Resolvers by Type | Sorts your GraphQL requests by the associated type to help break down which part of your API is being used the most |
| Error Counts | Total number of errors broken down by hour |
| Application Logs | Table of all the latest logs for your API |
Our goal is to give you as much transparency as we can into your GraphQL API, so you can make the best app possible. Please email us at<EMAIL_ADDRESS>if you have any requests for metrics you would like to see that would help you better measure and analyze the success of your app.
Export Schema
Exporting your schema can be useful if you want to see what the raw JSON version of your data model looks like.
Scaphold manages two versions of your schema:
Standard GraphQL: Download a standard version of your GraphQL schema that you can use with any GraphQL server.
Scaphold Schema: Save a copy of your up-to-date GraphQL schema that you can use to create an app on Scaphold.
Fork App
Forking an app will make an exact copy of the app in a new environment. That means the new app will automatically configure itself to be identical to the source but it will have its own data, schema, and API. This is useful for testing schema changes before making any migrations to your production applications. Once you have tested your changes and manually merged them into your production app you can safely remove the fork.
You can fork an app in 2 ways:
Apps Page > Underneath each API link for each app panel (top-right)
Settings Page > Advanced
Teams
App development is always more fun and faster in teams! Which is why we’ve provided you the ability to collaborate with others on apps.
There are two ways to add a teammate:
Bottom left of the left side panel in the app-view.
Settings Page > Team
In addition to adding a teammate, you may also want to remove a teammate. To do so, navigate to the Settings Page > Team, click on the bubble under Teammates that designates the user you wish to remove from this app, and a dropdown will appear that will allow you to remove that user.
Regions
Scaphold is now deployed in multiple regions and counting! With growing demand for Scaphold’s service by developers around the world, we’re always open to setting up new infrastructure so that your data lives closer to you.
Currently, we’re deployed in…
US West (Oregon)
EU West (Ireland)
More to come. If you wish to have Scaphold deployed on a data center in your region, please contact us at<EMAIL_ADDRESS>To switch between regions, select the region you wish to use in the header bar of the Scaphold dashboard.
Admin Tokens
With admin tokens, you have the ability to bypass any permissions that have been set in your API. By setting this as Authorization: Bearer {ADMIN_TOKEN}
in your header
like any other auth token in Scaphold, you become an admin user. This is useful for importing or exporting data, any one-off data management tasks that need to be done, or scheduled jobs.
To create a admin token, navigate to Settings Page > Admin Tokens > Create
Token Expiration
Scaphold gives you full control over how your tokens are managed for authentication. As such, we provide the ability to control the expiration of the JsonWebToken (JWT) measured in seconds
from when it was issued. This refers to the token issued during mutation calls such as loginUser
, or any social auth flow.
To configure the token expiration time, please navigate to Settings Page > Advanced
Starter Kits
The simplest way to get up and running is to use one of our GraphQL ready starter kits on GitHub.
React & Apollo
Quickstart
1) Go to Scaphold.io.
2) Create an account and dataset.
3) Change the URL in the API manager (config.js
) in the boilerplate to point to your unique Scaphold.io API URL.
5) Install dependencies: npm install
4) Run with: npm start
Deployment
Note: For development, you only need to run npm start
1) Run npm run build to transpile ES6 code from the src/
directory to JavaScript in the lib/
directory.
2) Set the environment variable process.env.NODE_ENV = 'production'
to let server.js
know to run the code in the lib/
directory.
React & Relay
Snippet from our React-Relay boilerplate (JavaScript only)
// js/routes/HomeRoute.js
query {
viewer {
${Component.getFragment('allHackerNewsItems', {orderBy: variables.orderBy})}
}
}
. . .
// js/components/HackerNewsClone/Home.js
fragment on Viewer {
allHackerNewsItems (first: 10, orderBy: $orderBy) {
edges {
cursor
node {
id,
createdAt,
modifiedAt,
title,
score,
url,
author {
id,
username
}
}
}
}
}
Run this code by running npm start
in the base directory of the application. Once the application has built, go to localhost:3001
in your browser to access your new website.
Congratulations! you’ve successfully launched your first website with Scaphold. How is this all working? When the website runs, the starter kit points to a Scaphold endpoint that we’ve created with some basic data. Your website leverages the GraphQL server that we’ve already set up for you, so you can start creating users and logging in. After you’ve successfully logged in, you’ll be able to see a HackerNews clone with several articles listed. Getting this list of items is another request that is being made via a GraphQL query.
To demonstrate, upon logging in, here is the query that is being made to fetch the HackerNews data:
These two pieces of code go together to form one query that grabs the first 10 HackerNewsItems from the app’s dataset with the corresponding return fields like title, score, url, and author. To see what other queries you can make, feel free to navigate to the GraphiQL page from the header of the website and open up the document explorer on the top right. From here, you’ll find a reference for query and mutation templates that are allowed for this particular app, and a sandbox to execute these particular requests.
If you would like to have a more involved or custom dataset to play around with, the beauty of Scaphold is that you can create your own in a matter of minutes! Jump right into the tutorial to start building your first app.
This starter kit is yours to keep, and if you have any questions, don’t hesitate to reach out to us through our chat support or email us at<EMAIL_ADDRESS>But wait, there’s more! Here’s the list of boilerplates that we provide that works right out of the box with Scaphold:
- React & Relay
- React Native & Relay
- More to come… On the docket: iOS & Android
Angular2 & Apollo Client
Download angular2-apollo-client-webpack-starter
A GraphQL-ready starter kit for Angular2 using Apollo Client and Webpack. This starter kit is built to seamlessly integrate with the scaphold.io’s powerful GraphQL-as-a-Service platform.
Out of the box, the application handles authentication and exhibits pagination using GraphQL connections. The template also comes packed with a few other goodies.
Table of contents
# clone our repo
# Take the master repo if you want to plug your own styling framework
$ git clone https://github.com/scaphold-io/angular2-apollo-client-webpack-starter.git my-app
# The AngularMaterial branch comes preloaded with the popular angular2-material library
$ git clone -b AngularMaterial https://github.com/scaphold-io/angular2-apollo-client-webpack-starter.git my-app
# update /my-app/src/config.ts with your scaphold api url
$ cd my-app
$ vi src/config.ts
# install the dependencies with npm
$ npm install
# Your scaphold url will look something like https://api.scaphold.io/graphql/my-awesome-app-alias
# start the server
$ npm start
Quick start
- Go to scaphold.io and create an account.
- Create an application and get your GraphQL API’s url from the dropdown in the nav bar.
- A Scaphold app comes with a customizeable GraphQL API that you can immediately use to start building your Angular2 application.
- Each API contains a default
User
type that handles authentication necessities like password encryption out of the box. This template is built to seamlessly work with a fresh scaphold.io app.
- Setup this starter kit
Your application is now hooked up to a production GraphQL API and is ready for you to build an awesome app.
Go to http://localhost:8080 to start using your API.
What’s included?
- Apollo Client - A powerful, easy to use GraphQL client.
- Webpack - A popular, well designed module bundler for JavaScript projects.
- ES6, and ES7 support with babel.
- Source maps for debugging.
- Development server with live reload via webpack-dev-server.
- Production builds with cache busting.
- Unit testing via karma and jasmine.
- Integration testing via protractor.
- Code coverage when tests are run.
Project Structure
angular2-apollo-webpack-starter/ ├── src/ # Project Root │ ├── app/ # Angular2 Application │ │ ├── about/ # about, home, login, register all contain Angular components │ │ ├── home/ │ │ ├── login/ │ │ ├── register/ │ │ ├── shared/ # shared contains Angular services. e.g. auth.service.ts │ │ ├── app.component.html # Each component has a X.component.html template file │ │ ├── app.component.scss # Component specific sass styling │ │ ├── app.component.ts # The component controller │ │ ├── app.component.e2e-spec.js # Protractor integration tests │ │ ├── app.component.spec.ts # Karma unit tests │ │ ├── app.routes.ts # Angular application router │ │ └── client.ts/ # Apollo client initialization │ ├── public/ # Public static assets │ │ ├── img/ │ │ └── index.html │ ├── style/ # Application wide sass styling │ │ └── app.scss │ ├── config.ts # Contains your API's url. │ ├── main.ts # Application entry point │ ├── polyfills.ts │ └── vendor.ts # Update application dependencies here ├── typings/ # Contains TypeScript definitions │ ├── globals/ │ ├── modules/ │ └── index.d.ts ├── package.json ├── karma-shim.js ├── karma-conf.js ├── protractor.conf.js ├── tsconfig.json ├── tslint.json ├── typedoc.json ├── typings.json ├── webpack.config.js ├── LICENSE └── README.md ```
Documentation
@Apollo({
client,
queries(context) {
return {
data: {
query: gql`
query AllUsers($first: Int, $after: String, $last: Int, $before: String) {
viewer {
allUsers(first: $first, after: $after, last: $last, before: $before) {
edges {
node {
id
username
lastLogin
createdAt
modifiedAt
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}
}
`,
variables: {
first: context.first,
after: context.after,
last: context.last,
before: context.before
},
forceFetch: false,
returnPartialData: true,
pollInterval: 10000
}
};
}
})
Apollo Client
Apollo Client is an easy to use, framework agnostic GraphQL client library that makes it easy to integrate GraphQL with your app. In our experience it can be a little easier to work with than Relay and allows you to use non-React frameworks like Angular 2! Under the hood, Apollo Client uses the Redux state container for caching so that your queries run quickly and your data is always in sync.
The Apollo Decorator
Angular2 uses decorator functions to attach metadata to classes. Components use @Component, services use @Injectable, and classes that need GraphQL functionality use @Apollo. Take a look at the HomeComponent in src/home/home.component.ts
.
The Apollo decorator allows you to define queries and mutations and make them reactive so that your data stays up to date as properties in your component class change. @Apollo takes a single object with the keys client
, queries
, mutations
as a parameter and returns an object that maps to properties in the decorated class.
For the example above note how we have property data: any
in our HomeCompnoent class. We can access the data returned by the query defined in the @Apollo decorator via the data
property. Also note how the queries function in the parameter exposes a single context
argument. This context
points to the scope of the decorated class so that you can directly reference your component’s state properties. This is extra awesome because Apollo Client automatically makes the queries defined in the @Apollo decorator reactive. This means that when properties in your class change so will your data. Apollo implements a bunch of cool tricks to make this efficient such that it only issues new API requests when needed and uses cached data in Redux for the rest.
You can find more documentation here for Apollo Client and Angular2Apollo (a native wrapper for Angular2)
Scaphold
Scaphold is a GraphQL-as-a-Service platform that makes it really easy to create production backends using GraphQL. Their web interface greatly simplifies the process of defining a GraphQL schema, integrations make adding other services like social authentication, stripe payments, email support, and push notifications a breeze, and the data exploration tools let you comb through your data without having to write GraphQL.
This template was created to work seamlessly with a fresh scaphold.io application and already has user registration and authentication configured for you. The HomeComponent src/home/home.component.ts
also includes and example of how you can easily implement pagination using Connections on Scaphold.
Pagination
Note: We advise you only use (
first
andafter
) OR (last
andbefore
) at any given time. Although you technically can use both at the same time it can lead to unexpected results. See the Cursor Connections Spec for more details.
query AllUsers($first: Int, $after: String, $last: Int, $before: String) {
viewer {
allUsers(first: $first, after: $after, last: $last, before: $before) {
edges {
node {
id
username
lastLogin
createdAt
modifiedAt
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}
}
It also binds the connection arguments to 4 properties in the HomeComponent class.
variables: {
first: context.first,
after: context.after,
last: context.last,
before: context.before
}
A necessary part of any application is the ability to paginate through data in arbitrarily sized pages. To do this in GraphQL, we use Connections. A Connection is a standards based approach to pagination that offers a efficient and straigh-forward pagination mechanism.
A GraphQL connection field takes 4 arguments
first
: An integer specifying the count of objects following theafter
cursor in a sequence that should be included in the response.after
: A string ‘cursor’. This is an opaque identifier that points to an object in a sequence. It specifies where our page slice should start in a sequence and is used withfirst
. Results are bounds-exclusive and will start strictly after this cursor.last
: An integer specifying the count of objects precursing thebefore
cursor in a sequence that should be included in the response.before
: A string 'cursor’. This is an opaque identifier that points to an object in a sequence. It specifies where our page slice should end in a sequence and is used withlast
. Results are bounds-exclusive and will start strictly before this cursor.
The HomeComponent defines the following GraphQL query in the @Apollo decorator function.
With this setup, we are able to immediately start paginating through our User data by changing the first
, after
, last
, and before
properties in the HomeComponent class. Apollo Client automatically handles making our query reactive and will make sure the presented data stays in sync with our class properties.
const edgeCount = this.data.viewer.allUsers.edges.length;
const afterCursor = (edgeCount > 0) ? this.data.viewer.allUsers.edges[edgeCount - 1]['cursor'] : null;
this.after = afterCursor;
this.first = this.defaultPageSize;
this.before = null;
this.last = null;
Try out different values to experiment with Cursor Connection pagination.
Installing
# For or clone this repo # Take the master repo if you want to plug your own styling framework $ git clone https://github.com/scaphold-io/angular2-apollo-client-webpack-starter.git my-app # The AngularMaterial branch comes preloaded with the popular angular2-material library $ git clone -b AngularMaterial https://github.com/scaphold-io/angular2-apollo-client-webpack-starter.git my-app # update /my-app/src/config.ts with your scaphold api url $ cd my-app $ vi src/config.ts # install the dependencies with npm $ npm install # Your scaphold url will look something like https://api.scaphold.io/graphql/my-awesome-app-alias # start the server $ npm start
What you need to run this app:
* node
and npm
* We tested this template with Node (v4.x
+) and NPM (3.x
+) but others will most likely work as well
Running the app
After you have installed all dependencies you can now run the app with: npm start
It will start a local server using webpack-dev-server
which will watch, build (in-memory), and reload for you. The port will be displayed to you as http://localhost:8080
.
Developing
Building
- single run:
npm run build
- build files and watch for changes:
npm run watch
Generating Documentation
npm run docs
- This uses TypeDoc and understand JavaDoc style tags
Testing
1. Unit Tests
- single run:
npm test
- live mode:
npm run test-watch
2. Integration Tests
- single run:
npm e2e
- live mode:
npm e2e-live
Contributing
Please feel free to fork this project and contribute. Send us a pull request and we’d be happy to merge :)
Changelog
December 7, 2016
Multiple Regions: We’re launched to two regions now - US West (Oregon) and EU West (Ireland). Please message us if you have a need for a new data center in your region!
Super Tokens: Create a new super token with a click of a button. Use this for administrative tasks.
Token Expiration: Configure the expiration time of your JWT tokens for your users.
Errors
A typical Scaphold response
type ScapholdResult {
data: any
errors: [ScapholdError]
}
type ScapholdError {
message: string!
code: ErrorCode
param: string
}
| Error Code | Meaning |
|---|---|
| 200 | OK – Everything is working as expected |
| 400 | Bad Request – There was an error in your request. |
| 401 | Unauthorized – Your API token is wrong or missing |
| 402 | Payment Required – You have exceeded the free pricing teir. Please input a payment option via the Account page. |
| 403 | Forbidden – You have a valid API token but you do not have permission to this page. |
| 404 | Not Found – The specified resource could not be found. |
| 429 | Too Many Requests – You have approached the 60 req/s limit. Please try to spread out your requests more evenly. |
| 440 | Login Failure – We either couldn’t find a user with that email or your password was incorrect. |
| 441 | Registration Failure – Invalid registration information. E.G. a user with that username already exists. |
| 500 | Internal Server Error – We had a problem with our server. Please contact us via slack or try again later. |
| 503 | Service Unavailable – We’re temporarially offline for maintanance. Happens very rarely. |
FAQ
Can’t find what you’re looking for? Contact us on Slack!
What can I build?
Whether you’re building a mobile or web app, we’ve got you covered. Scaphold will help you through every level of the development process. Use our schema designer to structure your data, GraphiQL to prototype your queries and export them directly into your client applications, monitor your application in our dashboard, and integrate popular services to extend your API.
Can I trust Scaphold when a service like Parse shutdown?
YES! We are Ycombinator backed company and are going strong! To help ease your mind, it is important to note that Facebook shut down Parse because it no longer fit in with their core strategy. There is a still a need for powerful BaaS products and we are here to bring you one.
In the extremely unlikely event that we do shut down then you still don’t need to worry! GraphQL is a 100% open spec and you have the freedom to build a GraphQL API just like the ones we expose to you! You might lose some of the bells and whistles such as our seamless integrations but your business will survive and your client application code will remain the same!
How do we compare with other BaaS products?
We think GraphQL is the best way to build apps. It’s that simple. There are other existing services like Firebase and Kinvey but they suffer from the same problems that have been plaguing BaaS solutions for years. This means vendor lock-in, complicated REST APIs, limited SDKs, and lack of flexibility. Scaphold turns this around and presents you with a standardized, extensible, and flexible GraphQL backend that can fit all kinds of application needs.  On top of all of this, our pricing model is the best in the industry so you can squeeze as much out of every dollar as possible!
What is our SLA?
Although we do not currently expose a standard SLA, our services run on a distributed cluster that was designed to prevent a single point of failure from bringing down our services. In the unlikely event that our services do go down for a time, we have systems working around the clock that will let us know so we can quickly fix the problem. You can trust that we are doing everything we can to provide the best possible experience for our clients and will continually push for 6 nines.
Who is using GraphQL?
The biggest proponent of GraphQL by far is Facebook. They designed GraphQL to solve many of the problems that they experienced when building many of their web and mobile products and they are using the technology to power more and more of their products. It’s not just Facebook however! Companies like Twitter, Meteor, and Kadira are also using GraphQL heavily and the feedback has been overwhelmingly positive.
How do I get support?
It’s our goal to make your life as a developer as easy as possible and that means haveing great customer support. The best way to get support is either using the Intercom chat bubble on this page or to join us on Slack at Scaphold Slack. Please feel free to contact us at any hour of the day and we will respond as soon as possible!


Kate Middleton is now reportedly one of Queen Elizabeth's favourite Royals
by Entertainment DeskWhile it has already been established that Queen Elizabeth is a Meghan Markle stan, the British monarch also counts Kate Middleton as one of her favorite royals.
Despite tabloid reports claiming they’re not close, it turns out Kate has recently become in the Queen’s good graces, and it makes sense. Amid the drama surrounding the royal family this year, including feuds and lawsuits, the duchess has remained calm in the wake of controversy, reported InStyle.
“The Queen is a fan,” a source told Vanity Fair‘s royal reporter Katie Nicholl. “Kate is unflappable. Her mantra is very like the Queen’s keep calm and carry on approach in life, and she has the added benefit of making the Royal Family seem almost normal and in touch.”
Recently, Kate has been praised for appearing just like your average mom. Last month, she was spotted buying her children’s Halloween costumes at a supermarket. It’s safe to say that level of relatability has won over royal fans.
“She’s very much the girl next door, from how she dresses to how she behaves and interacts with people on engagements,” royal biographer Claudia Joseph explained in the VF article. “She is likeable and relatable and that has made her very popular,” Joseph said, adding that Kate’s not just well-liked in the UK (and at Kensington Palace), but also “around the world.”
As an added bonus, it’s reported that Kate plans to raise her three children — Prince George, Princess Charlotte, and Prince Louis — with the same sense of realness.
According to The Sun, Kate wants to provide her kids with an ordinary childhood. “Kate has always wanted the children to live in the real world. She and William don’t want them to live in a gilded bubble,” a source told the outlet. “They want them to grow up having the same experiences as other kids.”


EVO HTML to PDF Converter allows you to insert existing PDF documents before the HTML to PDF conversion result and to add existing PDF documents after conversion result using the
AddStartDocument() and AddEndDocument() methods of the HtmlToPdfConverter.PdfDocumentOptions object. The PDF documents can be loaded from files or streams or they can be
existing Document objects previously created in your application.
The options you can set in this demo are:
-
HTML Page to Convert URL. The URL of the HTML document to be converted to PDF. Existing PDF files will be added before and after the conversion of this page
-
PDF File to Insert Before Conversion. A link to the PDF file to be inserted before the HTML to PDF conversion result
-
PDF File to Add After Conversion. A link to the PDF file to be added after the HTML to PDF conversion result


 It is easy to…
By Suzanne Mastaw
Add
Slow Cooker Pot Roast
A few packaged mixes make it so easy to serve a tender beef pot roast and savory gravy, all…
Add
By Keylimeone
Slow Cooker Pulled Pork
Using root beer to season slow cooked pork tenderloin makes a novel, yet tasty…
By Livie's Mommy
Add
Best Chocolate Chip Cookies
Crisp edges, chewy middles, and so, so easy to make. Try this wildly-popular chocolate chip…
By Dora
Add
The Best Thai Coconut Soup
This recipe uses a lot of ingredients common in Thai cooking to make a delicious and spicy…
By Jessica
Add
Go! Kitchen
LOGIN


50s - The beginning
Some songs from the era:
Jailhouse Rock
Shake, Rattle & Roll
Rave On
La Bamba
Johnny B. Goode


auto-hash
automatically create hash info for files
Features
- automatically generate MD5 for all files in list
- output all heshes as an Object to the target file
- add hash value to filename originalFilename.hash.ext（rename or create a copy of the original file）
Install
npm install auto-hash
or
yarn add auto-hash
Usage
run as Node.js script directly
autohash -c auto-hash.config.json
// add -c or -config to set configuration file
import autoHash as a module
const autoHash = require('auto-hash');
autoHash({
config: './auto-hash.config.json',
});
// add 'c' or 'config' to set configuration file
// or you can pass configuration directly
autoHash({
files: [{
file: "test/index.js",
name: "testIndex"
}],
output: {
file: "test/test-hash.js"
},
len: 8,
rename: false,
copy: true
});
output example
return as an object when call
autoHash()
{ testIndex: '5745abcc' }
output as a file when
output.file
is provided (the object will still be returned inautoHash()
)module.exports = { testIndex: '5745abcc' };
About file path
! All file paths shoud be relative to working directory
Configuration example
configuration file is in .json
{
// 'files' is the list that contains all files to be hashing
"files": [
{
"file": "src/index.js", // (required) set file path in 'file' property
"name": "index" // (optional) set 'name' property to change the property of hash in output object, default to filename
}
],
"output": {
"file": "src/auto-hash.js" // (optional) path of output file
},
"len": 10, // (optional) the length of hash string to be used, default to full string.
"rename": false, // (optional, false) rename the original file to originalFilename.hash.ext
"copy": true // (optional, false) create a copy in originalFilename.hash.ext
}
LICENSE
MIT License
Copyright (c) 2017 Cheng Gu
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.