Skip to content

Instantly share code, notes, and snippets.

@belachkar
Created February 15, 2021 02:46
Show Gist options
  • Save belachkar/b909a034c595f494475cdb8532671c33 to your computer and use it in GitHub Desktop.
Save belachkar/b909a034c595f494475cdb8532671c33 to your computer and use it in GitHub Desktop.

React Apollo GraphQL basic implementation

Frontend app

React application using Apollo to request a GraphQL API.

.eslintignore:

public
.cache
package-lock.json

eslintrc.json:

{
	"env": {
		"browser": true,
		"es2021": true
	},
	"extends": [
		"eslint:recommended",
		"plugin:react/recommended"
	],
	"parserOptions": {
		"ecmaFeatures": {
			"jsx": true
		},
		"ecmaVersion": 12,
		"sourceType": "module"
	},
	"plugins": [
		"react"
	],
	"rules": {
		"no-unused-vars": "warn"
	}
}

package.json:

{
  "dependencies": {
    "@apollo/client": "^3.3.9",
    "graphql": "^15.5.0",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "eject": "react-scripts eject",
    "wait:msg": "echo Waiting... 5 seconds! (Please save and close all opened tabs - Press Ctrl+C to Exit)",
    "git:deploy": "npm run git:merge && npm run git:push",
    "wait": "npm run wait:msg && sleep 5",
    "git:push": "git push origin dev && git push origin master",
    "git:merge": "git status && npm run wait && git checkout master && git merge dev && git checkout dev"
  },
  "eslintConfig": {
    "extends": [
      "react-app"
    ]
  }
}

App.js

import React from 'react';
import './App.css';
import logo from './logo.jpg';
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
import Launches from './components/Launches';

const client = new ApolloClient({
  uri: 'http://localhost:5000/graphql',
  cache: new InMemoryCache(),
});

function App() {
  return (
    <ApolloProvider client={client}>
      <div className="App">
        <div className="container">
          {/* Logo */}
          <img id="logo" src={logo} alt="SpaceX" />
          <Launches />
        </div>
      </div>
    </ApolloProvider>
  );
}

export default App;

components/LaunchItem.js:

import React from 'react';
import { launchType } from '../types';

const Launch = ({ launch }) => {
  const {
    flight_number,
    mission_name,
    launch_success,
    launch_date_local,
  } = launch;

  return (
    <div className="card card-body mb-3">
      <div className="row">
        <div className="col-md-9">
          <h4>Misson: {mission_name}</h4>
          <p>Date: {launch_date_local}</p>
          <span style={{ color: `${launch_success ? 'green' : 'red'}` }}>
            {launch_success ? 'Success' : 'Failed'}
          </span>
        </div>
        <div className="col-md-3 d-flex justify-content-end">
          <button className="btn btn-primary">
            Details
          </button>
        </div>
      </div>
    </div>
  );
};

Launch.propTypes = {
  launch: launchType,
};

export default Launch;

components/Launches.js:

import { gql, useQuery } from '@apollo/client';
import React from 'react';
import Launch from './LaunchItem';

const LAUNCHES_QUERY = gql`
  query LaunchesQuery {
    launches {
      flight_number
      mission_name
      launch_success
      launch_date_local
    }
  }
`;

export const Launches = () => {
  const { loading, error, data } = useQuery(LAUNCHES_QUERY);

  return (
    <div>
      <h1 className="display-4 my-3">Launches</h1>
      {loading && <h4>Loading...</h4>}
      {error && <p>Error :{error.message}</p>}

      {/* Launches list */}
      {data &&
        data.launches.map((launch, i) => (
          <Launch key={`launch_${i}_${launch.flight_number}`} launch={launch} />
        ))}
    </div>
  );
};

export default Launches;

types/index.js: propTypes

import { number, string, bool, shape } from 'prop-types';

// Rocket Type
export const rocketType = shape({
  rocket_id: string.isRequired,
  rocket_name: string.isRequired,
  rocket_type: string,
});

// Launch Type
export const launchType = shape({
  flight_number: number.isRequired,
  mission_name: string.isRequired,
  launch_success: bool,
  launch_date_local: string,
  launch_year: string,
  rocket: rocketType
});

Backend app

Node.JS server using Express framework.

package.json:

{
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "server": "nodemon server.js",
    "client": "npm start --prefix client",
    "dev": "concurrently \"npm run server\" \"npm run client\""
  },
  "dependencies": {
    "axios": "^0.21.1",
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "express-graphql": "^0.12.0",
    "graphql": "^15.5.0"
  },
  "devDependencies": {
    "concurrently": "^5.3.0",
    "nodemon": "^2.0.7"
  }
}

server.js:

const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const cors = require('cors');
const schema = require('./schema');

const app = express();

// Allow cross origin
app.use(cors());

// Add GraphQL endpoint
app.use(
  '/graphql',
  graphqlHTTP({
    schema,
    graphiql: true,
  })
);

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`App listening on port ${PORT}!`));

**schema.js:

const axios = require('axios');
const {
  GraphQLObjectType,
  GraphQLInt,
  GraphQLString,
  GraphQLBoolean,
  GraphQLList,
  GraphQLSchema,
} = require('graphql');

// Rocket Type
const RocketType = new GraphQLObjectType({
  name: 'Rocket',
  fields: () => ({
    rocket_id: { type: GraphQLString },
    rocket_name: { type: GraphQLString },
    rocket_type: { type: GraphQLString },
  }),
});

// Launch Type
const LaunchType = new GraphQLObjectType({
  name: 'Launch',
  fields: () => ({
    flight_number: { type: GraphQLInt },
    mission_name: { type: GraphQLString },
    launch_year: { type: GraphQLString },
    launch_date_local: { type: GraphQLString },
    launch_success: { type: GraphQLBoolean },
    rocket: { type: RocketType },
  }),
});

const SpaceXApiURL = 'https://api.spacexdata.com/v3';

// Root query
const RootQuery = new GraphQLObjectType({
  name: 'RootQueryType',
  fields: {
    launches: {
      type: new GraphQLList(LaunchType),
      resolve(parent, args) {
        return axios.get(`${SpaceXApiURL}/launches`).then((res) => res.data);
      },
    },
    launch: {
      type: LaunchType,
      args: {
        flight_number: { type: GraphQLInt },
      },
      resolve(parent, args) {
        return axios
          .get(`${SpaceXApiURL}/launches/${args.flight_number}`)
          .then((res) => res.data);
      },
    },
    rockets: {
      type: new GraphQLList(RocketType),
      resolve(parent, args) {
        return axios.get(`${SpaceXApiURL}/rockets`).then((res) => res.data);
      },
    },
    rocket: {
      type: RocketType,
      args: {
        id: { type: GraphQLString },
      },
      resolve(parent, args) {
        return axios
          .get(`${SpaceXApiURL}/rockets/${args.id}`)
          .then((res) => res.data);
      },
    },
  },
});

module.exports = new GraphQLSchema({
  query: RootQuery,
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment