GraphQL & React & Docker
Sebastian Wachter, Christophe Hoste, Immanuel Haag, Axel Forstenhäusler
Live slides & excersies
What is GraphQL?
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.
What is GraphQL?
- New API standard, alternative to REST
- Invented & open sourced by Facebook
- Declarative data fetching
- Specify exactly what you want from the API
- ONE single endpoint
Why GraphQL?
- Increased mobile usage
- Different clients
- Sloppy Networks
- Backend and frontend team communication
Initial reasons
Not only for React developers!
- C# .Net
- Clojure
- Elixir
- Erlang
- Go
- Groovy
Server libraries for GraphQL:
- Java
- JavaScript
- PHP
- Python
- Scala
- Ruby
Client side:
Any programming language
Additional informations
Companies using GraphQL:
Who is using GraphQL: https://graphql.org/users/
Conferences:
- San Francisco - GraphQL Summit in October
- Berlin - GraphQL Conf. Berlin in June
Request - Response
Blogging App
REST
GET api/users/:id
{
"id": "42",
"email": "alice@bob.gql",
"firstname": "Alice",
"lastname": "Bob",
…
}
REST
GET api/users/:id/posts
{
"posts": [
{
"id": "1",
"title": "Title 1",
"published": true,
"createdAt": "2019-04-10T18:29:16.983Z",
"content": "lorem ipsum ..."
},
…
]
}
GraphQL
POST /graphql
query {
user(where: {
id: "42"
}) {
firstname
posts(last: 3) {
title
published
}
}
}
{
"data": {
"user": {
"firstname": "Alice",
"posts": [
{
"title": "Title 1",
"published": true
},
…
]
}
}
}
GraphQL prevents
{
"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 ..."
},
…
]
}
- Imagine showing comments for every post!
=> 3 additional requests (api/posts/:id/comments)
Different clients - Web-Browser
How does GraphQL work?
- GraphQL is only a specifiation, not a concrete implementation!
GraphQL Resolvers
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!]!
}
GraphQL Resolvers
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!]!
}
GraphQL Resolvers
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!]!
}
GraphQL Resolvers
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!]!
}
Prisma
CRUD resolvers are generated for us when using Prisma!
GraphQL Schema Definition Language (SDL)
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)
}
GraphQL Schema Definition Language (SDL)
Scalar Types
- Int
- Float
- String
- Boolean
- ID
- DateTime
Field
- name and type
- age: Int
- age: Int!
Lists
- square brackets
- names: [String!]
- names: [String!]!
Schema Directive
- @defaultValue
- @unique
- @relation
- @id
- @createdAt
- @updatedAt
More
- Enum
- Interfaces
Queries - Simple
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"
}
]
}
}
Queries - Nested
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"
}
]
}
]
}
}
Queries - Filtering
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"
}
]
}
}
}
Queries - Filtering
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"
}
]
}
]
}
}
Operators
- _not
- _in
- _not_in
- _lt(e)
- _gt(e)
- _contains
- _not_contains
- _starts_with
- _not_starts_with
- _ends_with
- _not_ends_with
- _every
- _some
- _none
Queries - Ordering & Pagination
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 - Create
mutation {
createUser(data: {
email: "sw200@hdm-stuttgart.de"
firstname: "Sebastian"
lastname: "Wachte"
}) {
id
email
}
}
{
"data": {
"createUser": {
"id": "cjumf4py79m8p0b95wgbwt2mp",
"email": "sw200@hdm-stuttgart.de"
}
}
}
Mutation - Create & Connect
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 - Update
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 - Upsert
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 - Delete
mutation {
deleteUser(where: {
email: "sw200@hdm-stuttgart.de"
}) {
id
email
}
}
{
"data": {
"deleteUser": {
"id": "cjumf4py79m8p0b95wgbwt2mp",
"email": "sw200@hdm-stuttgart.de"
}
}
}
Subscriptions
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"
]
}
}
}
Deploy a test application
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
Deploy locally with docker
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
Challenges and outlook
- Authentication
- Permission Queries
Graphcool (BaaS)
=> Resolvers are needed
- Authorization
query ($user_id: ID!, $node_id: ID!) {
SomeUserExists(
filter: {
OR: [{
AND: [
{id: $user_id}
{id: $node_id}
]
}, {
AND: [
{id: $user_id}
{role: ADMIN}
]
}]
}
)
}
Additional informations
Wrapping existing REST-API in GraphQL
Use existing database in GraphQL
JSX & React introduction
Introduction
Names:
React / React.js or React JS
A JavaScript library for building user interfaces.
React JS !== React Native
Introduction
History
Initial release: May 2013
Created by Jordan Walke
at Facebook.
Introduction
Current state: 16.8.6 (March 27, 2019)
Used by:
Introduction
Hello-World example:
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('app')
);
JSX
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>;
JSX
In React the render logic is coupled with UI logic so it explains:
- How events get handled
- How state is changed
- How data is rendered
⇒ 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
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:
JSX
If you don't want to use JSX give this projects a try:
JSX-Expressions
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!
JSX-Expressions
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!
JSX-Expressions
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>;
}
JSX-Attributes
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
List rendering
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
Components
Components
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
Components - differences
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
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>
);
}
}
Modifying State
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>
);
}
}
State
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!
Components - Props
"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
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!
Conditional rendering
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>
}
</>
);
Components
Usually React applications have an App component as their entry point.
Splitting business logic into small reusable components is recommended.
Routing - Router
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)
Routing - Route
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} />
Routing - Link
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>
Routing - Redirect
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" />
React & GraphQL
Packages
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
Get started
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!
Get started
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:
Request data
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>
Request specific data
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>
Mutate data 1
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:
Mutate data 2
<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:
Wrap up
Docker
Introduction - Docker
- release of Docker as Open Source Project (2013)
- slogan: “Build, Ship, Run”
- ease of packaging and deploying applications
- focused on usability
- trigger for DevOps movement
Container Technologies
Success Stories
Success Stories
- 200+ Dockerized Apps
- Increased infrastructure efficiency by reducing the number of VMs.
- Increased agility and achieved rapid response to evolving customer needs.
-
Achieved more application portability over multiple environments.
Saved time, money and resources.
Success Stories
Docker != Container
user-friendly "Framework" / "Ecosystem" for handling Containers!
- Docker Client:
for interaction with Docker Daemon
shares a UNIX socket with the daemon
- Docker Daemon:
connects to the same UNIX socket as the client
responsible for starting, stopping and monitoring containers
- Docker Hub:
public registry for Docker images
Today? Not only Containers
Technical Stuff
- A software container is very similar to a shipping container
- Simply package a piece of software along with everything that is needed to make it work.
-
Software containers: isolated working environments for an application.
With necessary:- dependencies
- libraries
- binaries
- configurations
Technical Stuff
- Containers depend on an image layer system (reduces duplication, enables reusability & security) --> docker hub registry
- A Container is a Linux process, or many processes, which are running isolated from other processes on the system, using the chroot system call and some Linux kernel features such as cgroups and namespaces.
But why not simply use a VM?
Technical Stuff
VM
Container
VM vs. Container
VMs
- Virtualize a complete operating system (OS)
- Virtualize the kernel of the host
- Heavyweight
- Suffer from a small overhead as the Machine instructions are translated from Guest to Host OS
- Take a few mins to boot up
- Take much more storage as the whole OS kernel and its associated programs have to be installed and run
Containers
- Virtualize the required file system
- Use/share the kernel of the host
- Lightweight
- Provide near-native performance as compared to the underlying Host OS
- Can be booted up in a few secs as compared to VMs (testing & CI/CD)
- Take a lower amount of storage as the base OS is shared
Docker Workflow/Lifecycle
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
Web-Dev & Docker?
Why should Devs know Docker?
Web-Dev & Docker?
Demo
Web-Dev & Docker?
- Build, Ship, Run principle
- No more "but this works on my machine" fails
- Test new software really quick
- Sandbox your software --> keep your laptop clean (Spotify in container etc)
- Transition into cloud & CI/CD pipeline ready software!
--> State of the art! --> $
Example 1
Example 1
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
Example 2
Live React Setup Demo
Try it yourself
GraphQL
By Axel Forstenhäusler
GraphQL
GraphQL introduction and comparison to REST
- 560