Get introduced to the marvellous GraphQL universe! Discover all t`he benefits for adding End-to-end Type-Safety to your GraphQL App taking all the advantages of TypeScriot, GraphQL & React working together. Apollo, auto-gen Types & Hooks would build the pillars of a SpaceX demo ๐ฐ
- Understand Why, How & What's GraphQL
- Create a J/TS GraphQL Server
- Create a J/TS GraphQL Client
- Contribute with Open Source
๐ Server
- S0: JS GraphQL Server Demo
- S1: JS GraphQL Server
- S2: TS GraphQL Server
๐ Client
- S0: JS REST Client
- S1: JS GraphQL Client
- S2: TS GraphQL Client
๐ Essentials
- Node. Latest LTS Version (10.15.3).
- Git. Latest LTS Version (2.21.0).
- Github. You just need to create an account!
๐ค Nice to have
Open a terminal
Fork and clone this repository
git clone https://github.com/${๐ฉโ๐ป๐จโ๐ป}/e2e-ts-gql-workshop
Navigate to the created folder
cd e2e-ts-gql-workshop
Time -0, it'll be time to liftoff creating (from scratch) a GraphQL Server. TypeDefs, resolvers & context will be the boosters of the GraphQL JS implementation. Then, we'll take all the advantages of having a strongly typed GraphQL Schema to auto-generate the TypeScript types based on our GraphQL type definitions!
- S0: JS GraphQL Server Demo
- S1: JS GraphQL Server
- S2: TS GraphQL Server
In this step will create a demo JS GraphQL Server based on mock data! Summary
- Create folder structure
- Implement JS GraphQL Server
- Run Server
๐ Checkout first step
git checkout server-step-0
๐ Create server folder
mkdir server
๐ Create index.js inside the server folder
cd server
touch index.js
Let's implement a demo GraphQL server in our index.js
๐ Declare typeDefs
const typeDefs = gql`
# Entry Points
type Query {
launches: [Launch]
launch(id: Int!): Launch
}
# Types
type Launch {
flight_number: Int!
mission_name: String!
details: String
links: LaunchLinks
rocket: LaunchRocket
}
type LaunchLinks {
flickr_images: [String]
}
type LaunchRocket {
rocket_name: String!
}
`;
๐ Declare resolvers
const resolvers = {
Query: {
launches: (obj, args, context) => launches,
launch: (obj, args, context) =>
launches.find(launch => args.id === launch.flight_number)
}
};
๐ Declare server
const server = new ApolloServer({
typeDefs,
resolvers
});
๐ Call server
server.listen().then(({ url }) => console.log(`๐ Server ready at: ${url}`));
๐ Import mock data
const launches = [...]
๐ Add dependencies
const gql = require("graphql-tag");
const { ApolloServer } = require("apollo-server");
๐ Install dependencies
npm install graphql apollo-server
๐ Run server
node ./index.js
๐ Explore GraphQL API http://localhost:4000/
Well done ๐ช, commit your changes and let's get our hands dirty!
In this step we will connect the server with a database and we will implement an underfecthing solution
- Explore the DB
- Create DB connector
- Adapt server & resolvers
- Underfetching implementation
Let's discover how to access to SpaceX real data ๐
๐ Open Humongous
๐ Login on Get Started
and choose your fav MongoDB hosting provider
๐ Paste the SpaceX Public DB link
mongodb+srv://public:spacex@spacex-gcp-gpg0u.gcp.mongodb.net/spacex-api
๐ Click Next & Create Project
๐ Explore all the data collections
Now, we are gonna get rid of the mocks to use real data ๐ฅ
๐ Create db.js
file
touch db.js
๐ Connect to DB
const url =
"mongodb+srv://public:spacex@spacex-gcp-gpg0u.gcp.mongodb.net/spacex-api";
const getDB = async () => {
const client = await MongoClient.connect(url, {
poolSize: 20,
useNewUrlParser: true
});
return client.db("spacex-api");
};
module.exports = {
getDB
};
๐ Add mongodb
dependendy
const MongoClient = require("mongodb");
๐ Install mongodb
dependency
npm install mongodb
๐ Adapt our server on index.js
replacing the current definition with:
(async () => {
const db = await getDB();
const server = new ApolloServer({
typeDefs,
resolvers,
context: { db }
});
server.listen().then(({ url }) => console.log(`๐ Server ready at ${url}`));
})();
๐ Import the DB
const { getDB } = require("./db");
๐ Implement resolvers replacing the current with:
const resolvers = {
Query: {
launches: async (obj, args, context) => {
const data = await context.db
.collection("launch")
.find()
.limit(10)
.toArray();
return data;
},
launch: async (obj, { flight_number }, context) => {
const [data] = await context.db
.collection("launch")
.find({ flight_number })
.limit(1)
.toArray();
return data;
}
}
};
๐ Remove launches on index.js
๐ Run server
node ./index.js
๐ Explore explore real data through the GraphQL API (refresh your browser) http://localhost:4000/
๐ Add rocket
to LaunchRocket
type LaunchRocket {
rocket_name: String!
rocket: Rocket
}
๐ Include the Rocket
type
type Rocket {
id: ID!
name: String
description: String
cost_per_launch: Int
}
๐ Add the LaunchRocket
resolver
const resolvers = {
Query: {...},
LaunchRocket: {
rocket: async (obj, args, context) => {
console.log("obj: ", obj);
const [data] = await context.db
.collection("rocket")
.find({ id: obj.rocket_id })
.limit(1)
.toArray();
return data;
}
}
}
๐ Run the server & explore real data (refresh your browser)
node ./index.js
Look ๐ what you're getting on obj
when asking for launches { rocket { rocket { ... } } }
(don't forget to remove the console.log)
2/3 โ , creating APIs has never been easier, commit your changes and let's move on to the last step!
In this step will auto-generate TypeScript types based on our GraphQL implementation to make our API type safe
- Explore API & Codebase
- Evolve the API
- Generate TS types
- Evolve Safely the API
๐ Checkout
git checkout server-step-2
๐ Install dependencies & run the server
cd server
npm install
npm start
๐ Explore the API http://localhost:4000
๐ Explore the codebase
๐ Try to understand how this query is being resolved
{
history(id: 17) {
title
flight {
mission_name
rocket {
rocket {
name
cost_per_launch
}
}
}
}
}
๐ Add to Rocket typeDefs rocketByName
extend type Query {
...
rocketByName(name: String!): Rocket
}
๐ Add it resolver
rocketByName: async (obj, { name }, context) => {
const [data] = await context.db
.collection(collection)
.find({ name })
.limit(1)
.toArray();
return data;
};
๐ Run server
node ./index.js
๐ Explore GraphQL API testing the new evolution (refresh your browser) http://localhost:4000/
If everything went well, commit your changes!
๐ Checkout
git checkout server-step-3
๐ Install dependencies & run the server
npm install
npm run dev
๐ Create codegen.yml
file
touch codegen.yml
๐ Include the configuration
schema: src/schema/**/*.ts
overwrite: true
watch: true
require:
- ts-node/register
generates:
./src/types/types.d.ts:
config:
contextType: ./context#MyContext
plugins:
- typescript-common
- typescript-server
- typescript-resolvers
Indentation here is crucial!
๐ Install codegen
dependencies
npm install graphql-code-generator@0.16.0 graphql-codegen-typescript-common@0.16.0 graphql-codegen-typescript-resolvers@0.16.0 graphql-codegen-typescript-server@0.16.0
๐ Install dependencies
npm install
๐ Open a terminal and run
npm run dev
๐ Explore the API http://localhost:4000
๐ Open another terminal and run
npm run generate
๐ Explore types/types.d.ts
file
Commit your changes and let's wrap this up!
๐ Add again rocketByName
into the Rocket typeDefs
extend type Query {
...
rocketByName(name: String!): Rocket
}
๐ Explore the types/types.d.ts
file changes
๐ Add again it resolver
rocketByNome: async (obj, { nome }, context) => {
const [data] = await context.db
.collection(collection)
.find({ nome })
.limit(1)
.toArray();
return data;
};
๐ Type your rocket
's resolvers
const Query: QueryResolvers.Resolvers = {
...
}
๐ Import the QueryResolvers
type definitions
import { QueryResolvers } from "../../types/types";
๐ Fix all the erros that you could find with the help of TypeScript!
๐ Checkout
git checkout server-step-5
๐ Install dependencies & run the server
npm install
npm run dev
๐ Explore the GraphQL API http://localhost:4000/graphql ๐ค Explore the REST API http://localhost:4000/rest
๐ Explore the codebase
Take a look at the servers
folder, excluding that folder eveything is same than last step!
Approaching landing... we will create a React-Apollo client in JS (using hooks, of course). Later on we will evolve it safely generating the TypeScript types from our GraphQL documents!
- S0: JS REST Client
- S1: JS GraphQL Client
- S2: TS GraphQL Client
- Create folder structure
- Setup Suspense
- Fetch data from REST
๐ Checkout first step
git checkout client-step-0
๐ Install create-react-app
npm install --global create-react-app
๐ Create client folder
create-react-app client
๐ Add Suspense
into your index.js
ReactDom.render:
ReactDOM.render(
<Suspense fallback="Loading...">
<App />
</Suspense>,
document.getElementById("root")
);
๐ Import Suspense
:
import React, { Suspense } from "react";
๐ Change your App
component as follow:
function App() {
const launchesPast = useFetch("https://api.spacex.land/rest/launches-past");
return (
<React.Fragment>
{launchesPast.map(({ mission_name, details, links, rocket }) => (
<div key={String(mission_name)}>
<h3>
๐ฐ {mission_name} ๐ {rocket.name}
</h3>
<p>{details}</p>
<img src={links.flickr_images[0]} width="200" />
</div>
))}
</React.Fragment>
);
}
๐ Import useFetch
import useFetch from "fetch-suspense";
๐ Install fetch-suspense
cd client
npm install fetch-suspense
๐ Install dependencies & run the client
npm install
npm start
๐ Explore the Client http://localhost:3000
- Setup GraphQL Client
- Fetch data from GraphQL
๐ Create on index.js
a new Apollo Client
const client = new ApolloClient({
uri: "http://api.spacex.land/graphql"
});
๐ Include ApolloProvider
ReactDOM.render(
<ApolloProvider client={client}>
<Suspense fallback="Loading...">
<App />
</Suspense>
</ApolloProvider>,
document.getElementById("root")
);
๐ Import dependencies
import { ApolloProvider } from "react-apollo-hooks";
import ApolloClient from "apollo-boost";
๐ Install dependencies
npm install react-apollo-hooks apollo-boost graphql
๐ Write on 'App.js' the GraphQL query
const query = gql`
query getLaunches {
launchesPast {
mission_name
details
links {
flickr_images
}
rocket {
name: rocket_name
}
}
}
`;
๐ Add useQuery
to fetch data from the GraphQL API
const launchesPastRest = useFetch("https://api.spacex.land/rest/launches-past");
const {
data: { launchesPast = [] }
} = useQuery(query);
๐ Import useQuery
import { useQuery } from "react-apollo-hooks";
import gql from "graphql-tag";
๐ Run the client again
npm start
๐ Explore the Client http://localhost:3000
๐ Open your browser inspector tool and give a ๐ to both size & time GraphQL-REST calls!
Commit your changes! It's time to finish the workshop off ๐ช
- Evolve the Client
- Generate TS types
- Evolve Safely the Client
๐ Checkout last step
git checkout client-step-2
๐ Include launch_success
field into our query
const query = gql`
query getLaunches {
launchesPast {
mission_name
details
links {
flickr_images
}
rocket {
name: rocket_name
}
launch_success
}
}
`;
๐ Remove useFetch
call and log the query
to verify that launch_success
is there
function App() {
// const launchesPast = useFetch("https://api.spacex.land/rest/launches-past");
const {
data: { launchesPast }
} = useQuery(query);
console.log("launchesPast", launchesPast)
return (...);
}
๐ Display launch_success
function App() {
const {
data: { launchesPast = [] }
} = useQuery(query);
return (
<React.Fragment>
{launchesPast.map(
({ mission_name, details, links, rocket, launch_success }) => (
<div key={String(mission_name)}>
<h3>
๐ฐ {mission_name} ๐ {rocket.
}
</h3>
<p>{details}</p>
<h3>Success: {launch_success ? "โ
" : "โ"}</h3>
<img src={links.flickr_images[0]} width="200" />
</div>
)
)}
</React.Fragment>
);
}
๐ Explore the Client to see the new field http://localhost:3000
๐ Create codegen.yml
file
touch codegen.yml
๐ Include the configuration
schema: https://api.spacex.land/graphql
documents:
- src/**/*.tsx
overwrite: true
watch: true
generates:
./src/types/types.d.ts:
plugins:
- typescript-common
- typescript-client
๐ Install codegen
dependencies
npm install graphql-code-generator@0.16.0 graphql-codegen-typescript-common@0.16.0 graphql-codegen-typescript-client@0.16.0
๐ Install dependencies
npm install
๐ Open a terminal and run
npm start
๐ Explore the Client http://localhost:3000
๐ Open another terminal and run
npm run generate
๐ Explore types/types.d.ts
file
Fantastic, we are almost done. Don't forget to commit the changes!
๐ Type your returned data
const {
data: { launchesPast }
} = useQuery<GetLaunches.Query>(query);
๐ Import GetLaunches
type definition
import { GetLaunches } from "../types";
๐ Add the ships
field to the query
const query = gql`
query getLaunches {
launchesPast {
# ...
ships {
id
name
port: home_port
image
}
}
}
`;
๐ Display the ships
with the help of TypeScript auto-completion!
return (
<React.Fragment>
{launchesPast.map(
({ ..., ships }) => (
// ...
{ships.map(({ id, name, port, image }) => (
<div key={id}>
<h3>
โด {name} ๐ {port}
</h3>
<img src={image} alt="" width={200} />
</div>
))}
</div>
)
)}
</React.Fragment>
);
๐ Install Apollo GraphQL VS Code extension
๐ Create apollo.config.js
file:
module.exports = {
client: {
service: {
url: "https://api.spacex.land/graphql"
}
}
};
๐ Press Ctrl + Space Bar
inside your query ๐คฏ
๐ You're just finished all GraphQL Client steps, hoping that you've enjoyed & learned something new!
Don't hesitate to contact @swcarlosrj if you'd have any question!