Sebastian Wachter, Christophe Hoste, Immanuel Haag, Axel Forstenhäusler
QL The "QL" in GraphQL stands for query language.
Graph The "Graph" in GraphQL refers to the organized data on the application server in a graph structure.
Initial reasons
Server libraries for GraphQL:
Client side:
Any programming language
Companies using GraphQL:
Who is using GraphQL: https://graphql.org/users/
Conferences:
GET api/users/:id
{
"id": "42",
"email": "alice@bob.gql",
"firstname": "Alice",
"lastname": "Bob",
…
}
GET api/users/:id/posts
{
"posts": [
{
"id": "1",
"title": "Title 1",
"published": true,
"createdAt": "2019-04-10T18:29:16.983Z",
"content": "lorem ipsum ..."
},
…
]
}
POST /graphql
query {
user(where: {
id: "42"
}) {
firstname
posts(last: 3) {
title
published
}
}
}
{
"data": {
"user": {
"firstname": "Alice",
"posts": [
{
"title": "Title 1",
"published": true
},
…
]
}
}
}
{
"id": "42",
"email": "alice@bob.gql",
"firstname": "Alice",
"lastname": "Bob",
… posts are missing
}
Underfetching | Overfetching |
---|
{
"posts": [
{
"id": "1",
"title": "Title 1",
"published": true,
"createdAt": "2019-04-10T18:29:16.983Z",
"content": "lorem ipsum ..."
},
…
]
}
=> 3 additional requests (api/posts/:id/comments)
export const Query: QueryResolvers.Type = {
post: (parent, args, ctx) => {
return ctx.data.posts.find(post => post.id === args.id)!
},
}
type Query {
post(id: ID!): Post
}
type Mutation {
createUser(name: String!): User!
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
}
type User {
id: ID!
name: String
posts: [Post!]!
}
export const Mutation: MutationResolvers.Type = {
createUser: (parent, { name }, ctx) => {
const id = ctx.data.idProvider()
const newUser = { id, name, postIDs: [] }
ctx.data.users.push(newUser)
return newUser
}
}
type Query {
post(id: ID!): Post
}
type Mutation {
createUser(name: String!): User!
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
}
type User {
id: ID!
name: String
posts: [Post!]!
}
export const Post: PostResolvers.Type = {
...PostResolvers.defaultResolvers,
author: (parent, args, ctx) => {
return ctx.data.users.find(user => user.id === parent.authorId)!
},
}
type Query {
post(id: ID!): Post
}
type Mutation {
createUser(name: String!): User!
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
}
type User {
id: ID!
name: String
posts: [Post!]!
}
export const User: UserResolvers.Type = {
...UserResolvers.defaultResolvers,
posts: ({ postIDs }, args, ctx) => {
return ctx.data.posts.filter(post => postIDs.includes(post.id))
},
}
type Query {
post(id: ID!): Post
}
type Mutation {
createUser(name: String!): User!
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
}
type User {
id: ID!
name: String
posts: [Post!]!
}
CRUD resolvers are generated for us when using Prisma!
type Post {
id: ID! @id
createdAt: DateTime! @createdAt
updatedAt: DateTime! @updatedAt
title: String!
content: String!
published: Boolean! @default(value: false)
author: User! @relation(name: "PostsByUser")
}
type User {
id: ID! @id
email: String! @unique
firstname: String!
lastname: String!
createdAt: DateTime! @createdAt
updatedAt: DateTime! @updatedAt
imgUrl: String
posts: [Post!]! @relation(name: "PostsByUser", onDelete: CASCADE)
}
Scalar Types
Field
Lists
Schema Directive
More
query {
users {
id
email
firstname
lastname
}
}
{
"data": {
"users": [
{
"id": "cjultjpjk41t70b95ltbteew0",
"email": "af096@hdm-stuttgart.de",
"firstname": "Axel",
"lastname": "Forstenhäusler"
},
{
"id": "cjultojzy3j380b22vhjmyfck",
"email": "ch152@hdm-stuttgart.de",
"firstname": "Christophe",
"lastname": "Hoste"
}
]
}
}
query {
users {
firstname
posts {
published
title
}
}
}
{
"data": {
"users": [
{
"firstname": "Axel",
"posts": [
{
"published": false,
"title": "Post 1"
},
{
"published": false,
"title": "Post 2"
}
]
},
{
"firstname": "Christophe",
"posts": [
{
"published": false,
"title": "Post 3"
}
]
}
]
}
}
query {
user(where: {
email: "af096@hdm-stuttgart.de"
}) {
firstname
posts {
published
title
}
}
}
{
"data": {
"user": {
"firstname": "Axel",
"posts": [
{
"published": false,
"title": "Post 1"
},
{
"published": false,
"title": "Post 2"
}
]
}
}
}
query {
users(where: {
email_contains: "@hdm-stuttgart.de"
}) {
firstname
posts {
published
title
}
}
}
{
"data": {
"users": [
{
"firstname": "Axel",
"posts": [
{
"published": false,
"title": "Post 1"
},
{
"published": false,
"title": "Post 2"
}
]
},
{
"firstname": "Christophe",
"posts": [
{
"published": false,
"title": "Post 3"
}
]
}
]
}
}
query {
users(
orderBy: id_ASC // or DESC
first:/*or last*/ 30
after:/*or before*/ "cjultjpjk41t70b95ltbteew0" // id of Axel
) {
firstname
posts {
published
title
}
}
}
{
"data": {
"users": [
{
"firstname": "Christophe",
"posts": [
{
"published": false,
"title": "Post 3"
}
]
}
]
}
}
mutation {
createUser(data: {
email: "sw200@hdm-stuttgart.de"
firstname: "Sebastian"
lastname: "Wachte"
}) {
id
email
}
}
{
"data": {
"createUser": {
"id": "cjumf4py79m8p0b95wgbwt2mp",
"email": "sw200@hdm-stuttgart.de"
}
}
}
mutation {
createPost(data: {
title:"Post 4",
content:"Content 4",
author: {
connect: {
email: "sw200@hdm-stuttgart.de"
}
}
}) {
id
title
author {
id
email
}
}
}
{
"data": {
"createPost": {
"id": "cjumf6ofb9mpx0b95c98y1vy4",
"title": "Post 4",
"author": {
"id": "cjumf4py79m8p0b95wgbwt2mp",
"email": "sw200@hdm-stuttgart.de"
}
}
}
}
mutation {
updateUser(where: {
email: "sw200@hdm-stuttgart.de"
} data: {
lastname: "Wachter"
}) {
id
email
firstname
lastname
}
}
{
"data": {
"updateUser": {
"id": "cjumf4py79m8p0b95wgbwt2mp",
"email": "sw200@hdm-stuttgart.de",
"firstname": "Sebastian",
"lastname": "Wachter"
}
}
}
mutation {
upsertUser(where: {
email: "sw200@hdm-stuttgart.de"
} create: {
firstname: "Sebastian"
lastname: "Wachter"
email: "sw200@hdm-stuttgart.de"
} update: {
lastname: "Wachter"
}) {
id
email
firstname
lastname
}
}
{
"data": {
"upsertUser": {
"id": "cjumf4py79m8p0b95wgbwt2mp",
"email": "sw200@hdm-stuttgart.de",
"firstname": "Sebastian",
"lastname": "Wachter"
}
}
}
mutation {
deleteUser(where: {
email: "sw200@hdm-stuttgart.de"
}) {
id
email
}
}
{
"data": {
"deleteUser": {
"id": "cjumf4py79m8p0b95wgbwt2mp",
"email": "sw200@hdm-stuttgart.de"
}
}
}
subscription {
user(where: {
mutation_in: [CREATED, UPDATED, DELETED]
}) {
mutation
node {
lastname
}
previousValues {
lastname
}
updatedFields
}
}
{
"data": {
"user": {
"mutation": "UPDATED",
"node": {
"lastname": "Wachte"
},
"previousValues": {
"lastname": "Wachter"
},
"updatedFields": [
"lastname",
"updatedAt"
]
}
}
}
npm install -g prisma
mkdir hello-world
cd hello-world
# you will need a prisma account
prisma init
# choose Demo server + MySQL database
# choose a name
# choose a stage (dev)
# choose Don't generate
prisma deploy
type User {
id: ID! @id
name: String!
}
endpoint: https://eu1.prisma.sh/forstenhaeusleraxel/hello-world/dev
datamodel: datamodel.prisma
npm install -g prisma
mkdir hello-world
cd hello-world
# you will need a prisma account
prisma init
# choose Create new database
# choose MySQL
# choose Don't generate
docker-compose up -d
prisma deploy
type User {
id: ID! @id
name: String!
}
endpoint: http://localhost:4466
datamodel: datamodel.prisma
docker-compose.yml
=> Resolvers are needed
query ($user_id: ID!, $node_id: ID!) {
SomeUserExists(
filter: {
OR: [{
AND: [
{id: $user_id}
{id: $node_id}
]
}, {
AND: [
{id: $user_id}
{role: ADMIN}
]
}]
}
)
}
Wrapping existing REST-API in GraphQL
Use existing database in GraphQL
Names:
React / React.js or React JS
A JavaScript library for building user interfaces.
React JS !== React Native
Initial release: May 2013
Created by Jordan Walke
at Facebook.
Current state: 16.8.6 (March 27, 2019)
Used by:
Hello-World example:
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('app')
);
JavaScript XML / JavaScript Syntax Extension
Extension of the common JavaScript grammar for React
JSX is not valid JavaScript so it needs to be compiled to be browser conformant
Example:
const heading = <h1>This is JSX</h1>;
In React the render logic is coupled with UI logic so it explains:
⇒ loosely coupled units called "components" *
Example:
const Nav = () => (
<ul id="nav">
<li><a href="#">item1</a></li>
<li><a href="#">item2</a></li>
</ul>
);
*Components will be explained later
JSX needs to be compiled to JavaScript to be executable by a browser
Example:
const Nav = () => React.createElement("ul", {
id: "nav"
}, React.createElement("li", null, React.createElement("a", {
href: "#"
}, "item1")), React.createElement("li", null, React.createElement("a", {
href: "#"
}, "item2")));
const Nav = () => (
<ul id="nav">
<li><a href="#">item1</a></li>
<li><a href="#">item2</a></li>
</ul>
);
Will be compiled to:
If you don't want to use JSX give this projects a try:
Render a variable using curly braces {}
const name = 'Axel Forstenhäusler';
const element = <h1>Hello, {name}!</h1>;
ReactDOM.render(
element,
document.getElementById('app')
);
// Expected output: Hello, Axel Forstenhäusler!
all valid JavaScript expressions are available
(+,-,*, ...)
const formatName = ({ firstname, lastname }) => `${firstname} ${lastname}`;
const user = {
firstName: 'Axel',
lastName: 'Forstenhäusler'
};
const element = (
<h1>Hello, {formatName(user)}!</h1>
);
ReactDOM.render(
element,
document.getElementById('root')
);
// Expected output: Hello, Axel Forstenhäusler!
Since JSX is basically JavaScript you can use all methods, statements and operators like .map, if, ternary, etc.
const getGreeting = (user) => {
if (user) return <h1>Hello, {formatName(user)}!</h1>;
return <h1>Hello, Stranger.</h1>;
}
Use quotes to specify string literals as attributes:
const button = <button tabIndex="0">Click!</button>;
Use curly braces to embed JavaScript expressions in an attribute/prop
const image = <img src={avatarUrl} alt="avatar" />;
React DOM: camelCase property naming convention instead of HTML attribute names
For example:
class becomes className
tabindex becomes tabIndex
In JSX you just loop over the array and return the JSX you want to render:
const persons = [
'Axel',
'Christophe',
'Immanuel',
'Sebastian',
];
const App = () => persons.map(person => <p>{person}</p>);
// Expected result:
// Axel
// Christophe
// Immanuel
// Sebastian
Like other frameworks React is based on components.
Therefore the application consists of logically separated and reusable units.
There are two different types of components in React:
functional components
class components
Functional components:
const Welcome = ({ name }) => <h1>Hello, {name}!</h1>;
These are called "functional component" because they are literal JavaScript functions.
They also cannot have state or access
class Welcome extends React.Component {
render() {
const { name } = this.props;
return <h1>Hello, {name}</h1>;
}
}
Class components:
These may have state and may access component lifecycle hooks as well.
State represents a component's data. A component is able to modify its state by itself. State may also be passed to child components using props.
class App extends React.Component {
state = {
message: 'Hello I am a state message!',
};
handleClick = () => {
const { message } = this.state;
const newMessage = message.split('').reverse().join('');
this.setState({ message: newMessage });
}
render() {
const { message } = this.state;
return (
<div>
<h1>{message}</h1>
<button onClick={this.handleClick}>Reverse message</button>
</div>
);
}
}
You may modify a component's state using events and corresponding handler functions.
class App extends React.Component {
state = {
message: 'Hello I am a state message!',
value: '',
};
handleClick = () => {
const { message } = this.state;
this.setState({ message: message.split('').reverse().join('') });
}
handleChange = (e) => {
this.setState({ value: e.currentTarget.value });
}
render() {
const { message, value } = this.state;
return (
<div>
<p>{message}</p>
<button onClick={this.handleClick}>Reverse message</button>
<p>{value}</p>
<input type="text" value={value} onChange={this.handleChange} />
</div>
);
}
}
Things to remember about state in React:
Do not modify state directly (make local copies and use setState):
handleClick = () => {
const { data } = this.state;
data = `I modify ${data} now.`;
this.setState({ data });
}
Remember that setState is async!
"Props" stands for properties.
Using props we may pass data from a parent to a child component.
const Parent = () => <Child content="I got passed by the parent"/>;
const Child = ({ content }) => (
<>
<h3>I render the passed data:</h3>
<p>{content}!</p>
</>
);
// Expected result:
// I render the passed data:
// I got passed by the parent!
The child component may not modify the prop data. To do that it has to emit an event to the parent which will modify it according to its business logic.
Components can refer to other components
Example: an App component which renders several Welcome components
const Welcome = ({ name }) => <h1>Hello, {name}!</h1>;
const App = () => (
<>
<Welcome name="Axel" />
<Welcome name="Christophe" />
<Welcome name="Immanuel" />
</>
);
// Expected output:
// Hello, Axel!
// Hello, Christophe!
// Hello, Immanuel!
There are multiple ways to render JSX conditionally:
const IfExample = () => {
if (trueStatement) return <h1>True case</h1>;
return <h1>False case</h1>;
}
const InlineExample = () => trueStatement ? <h1>True case</h1> : <h1>False case</h1>;
const OnlyTrue = () => (
<>
{unreadMessages.length > 0 &&
<h2>You have {unreadMessages.length} unread messages.</h2>
}
</>
);
Usually React applications have an App component as their entry point.
Splitting business logic into small reusable components is recommended.
To enable routing in React you need to wrap your app between one of five router components:
import { BrowserRouter, HashRouter, MemoryRouter, Router, StaticRouter } from 'react-router';
BrowserRouter: Uses HTML5 history API (/newsfeed)
HashRouter: Uses a hash portion of the URL (#/newsfeed)
MemoryRouter: Keeps URL in memory (useful for Native)
Router: Low-level interface for other router components
StaticRouter: Never changes its location (server side rendering)
The Route component defines which component should be rendered matching the URL.
Route parameters can be accessed through a prop.
import { Route, Redirect, Link } from 'react-router-dom';
<Route path="/news" component={News} />
<Route path="/users" exact component={ProfileList} />
<Route path="/users/:id" component={SingleProfile} />
A Link component defines a link which leads to a route.
import { Route, Redirect, Link } from 'react-router-dom';
<Link to="/news">Navigate to newsfeed</Link>
A Redirect component is used to lead the user somewhere else.
For example after a successful login redirect the user to the newsfeed
import { Route, Redirect, Link } from 'react-router-dom';
<Redirect to="/news" />
To use GraphQL together with React we will use the React Apollo Client.
What we need:
Apollo Boost: helps setting up an Apollo Client
React Apollo: View layer intergration of Apollo Client for React
GraphQL Tag: Parsing the GraphQL queries
Create a client:
import ApolloClient from 'apollo-boost';
import { gql } from 'graphql-tag';
import { ApolloProvider } from 'react-apollo';
const App = () => (
<ApolloProvider client={client}>
... Your app here
</ApolloProvider>
);
const client = new ApolloClient({
uri: 'https://eu1.prisma.sh/forstenhaeusleraxel/evwa/dev',
});
Connect your client to React:
To access Apollo's features you need to wrap your app into the ApolloProvider:
Now we are ready to use!
Create a client:
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';
const App = () => (
<ApolloProvider client={client}>
... Your app here
</ApolloProvider>
);
const client = new ApolloClient({
uri: '<your-url>',
});
Connect yout client to React:
To access Apollo's features you need to wrap your app into the ApolloProvider:
To request data we use the Query component of react-apollo:
const GET_PLANTS = gql`
{
plants {
name
type
}
}
`;
import gql from 'graphql-tag';
import { Query } from 'react-apollo';
After that we write our query using graphql-tag:
Pass the query to the Query component and render the response data:
<Query query={GET_PLANTS} fetchPolicy="network-only">
{({ loading, error, data }) => {
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
return data.plants.map(({ name, type }) => (
<div key={name}>
<p>Name: {name}</p>
<p>Type: {type}</p>
</div>
));
}}
</Query>
const GET_PLANT = gql`
query Plant($name: String!) {
plants(where: { name: $name }) {
type
size
}
}
`;
To request specific data we need to define the query differently. GraphQL also checks whether the passed variable is of the correct type (in this case String).
Using the Query component we can pass a variables object:
<Query query={GET_PLANT} variables={{ name: 'ivy' }} fetchPolicy="network-only">
{({ loading, error, data }) => {
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
const [{ type, size }] = data.plants;
return (
<>
<p>Type: {type}</p>
<p>Size: {size}</p>
</>
)
}}
</Query>
For all kinds of mutations Apollo provides a Mutation component:
const CREATE_PLANT = gql`
mutation CreatePlant(
$name: String!
$type: String!
$size: Int!
) {
createPlant(data: {
name: $name
type: $type
size: $size
}) {
name
}
}
`;
import gql from 'graphql-tag';
import { Mutation } from 'react-apollo';
We define the mutation using graphql-tag again:
<Mutation mutation={ADD_TODO} ignoreResults>
{createPlant => (
<form
onSubmit={e => {
e.preventDefault();
createPlant({ variables: { name: name.value, type: type.value, size: size.value } });
}}
>
<input ref={node => name = node} type="text" placeholder="Name"/>
<input ref={node => type = node} type="text" placeholder="Type"/>
<input ref={node => size = node} type="number" placeholder="Size"/>
<button type="submit">Add plant</button>
</form>
)}
</Mutation>
Now we can pass the GraphQL statement to the Mutation component. We mutate data using a form and the React onSubmit handler.
We get the values from the inputs and pass them to the GraphQL statement:
user-friendly "Framework" / "Ecosystem" for handling Containers!
for interaction with Docker Daemon
shares a UNIX socket with the daemon
connects to the same UNIX socket as the client
responsible for starting, stopping and monitoring containers
public registry for Docker images
But why not simply use a VM?
VM
Container
VMs
Containers
FROM ubuntu:xenial
RUN apt-get update
RUN apt-get install -y nginx php7.0-fpm supervisor && \
rm -rf /var/lib/apt/lists/*
...
docker build -t mysuperimage .
mysuperimage:v2
mysuperimage:v2
Why should Devs know Docker?
Demo
--> State of the art! --> $
Live Demo Prisma Setup
version: '3'
services:
prisma:
image: prismagraphql/prisma:1.31
restart: always
ports:
- "4466:4466"
environment:
PRISMA_CONFIG: |
port: 4466
databases:
default:
connector: mysql
host: mysql
user: root
password: prisma
rawAccess: true
port: 3306
migrations: true
mysql:
image: mysql:5.7
restart: always
environment:
MYSQL_ROOT_PASSWORD: prisma
volumes:
- mysql:/var/lib/mysql
volumes:
mysql:
Requirements:
- Docker installed
- NPM installed
- CLI :)
To-Do:
$ npm install -g prisma
$ mkdir hello-world && cd hello-world
$ prisma init (create new database -> MySQL -> don't generate)
$ docker-compose up -d
$prisma deploy
Live React Setup Demo