Files
apollo/docs/source/tutorial/data-source.md
2018-11-07 09:26:27 -08:00

5.1 KiB

title, description
title description
2. Hook up your data sources Start here for the Apollo fullstack tutorial

Apollo data sources provide the best experience for fetching and caching data from REST endpoints, web services, and databases. It's a new pattern for loading data from various sources, with built-in support for deduplication, caching, and error handling.

Connect a REST API

To get started connecting to a REST API, install the apollo-datasource and apollo-datasource-rest packages:

npm install apollo-datasource-rest

The apollo-datasource-rest package exposes the RESTDataSource class that is responsible for fetching data from a given REST API. To define a data source for the REST endpoint, extend the RESTDataSource class and implement the data fetching methods that your resolvers require. Let's look at a simple example to understand how data sources work.

import { RESTDataSource } from 'apollo-datasource-rest';

export class MvrpAPI extends RESTDataSource {
  constructor() {
    super();
    this.baseURL = 'https://mvrp.herokuapp.com/api/';
  }

  async getAllCars() {
    return this.get('cars');
  }

  async getACar(plateNumber) {
    const result = await this.get('car', {
      plateNumber
    });

    return result[0];
  }
};

The https://mvrp.herokuapp.com/api/ endpoint is a simple REST API that returns data for cars. Furthermore, the MvrpAPI class implementation in the code above contains a getAllCars and getACar functions that wrap convenience methods provided by the RESTDataSource class for performing HTTP requests. In this example, the built-in get method used is responsible for GET requests.

Now that you have an understanding of how data sources work, let's hook it up for our tutorial app.

Create a new datasources folder inside the src directory. This folder will contain our data source files. Now, create launch.js within the datasources directory.

The REST API endpoint we'll use for our app is https://api.spacexdata.com/v2/. Add the endpoint as a base URL as shown in the code below:

src/datasources/launch.js

const { RESTDataSource } = require('apollo-datasource-rest');

class LaunchAPI extends RESTDataSource {
  constructor() {
    super();
    this.baseURL = 'https://api.spacexdata.com/v2/';
  }
}

module.exports = LaunchAPI;

The next step is to add methods to the LaunchAPI class that corresponds to the type of queries our UI will fetch from the server. According to our app specifications, we'll need to get all launches, and get a specific launch. So, let's take care of getting all launches.

...
async getAllLaunches() {
  const res = await this.get('launches');

  return res.map(launch => {
    return {
      id: launch.flight_number || 0,
      cursor: `${launch.flight_number || 0}-${launch.mission_name}`,
      mission: {
        name: launch.mission_name,
        patch: null, // what to do here?
      },
      year: launch.launch_year,
      date: launch.launch_date_unix,
      rocket: {
        id: launch.rocket.rocket_id,
        name: launch.rocket.rocket_name,
        type: launch.rocket.rocket_type,
      },
      launchSuccess: launch.launch_success,
    };
  });
}

In the code above, this.get('launches'), makes a GET request to https://api.spacexdata.com/v2/launches and stores the returned data in the res variable. The getAllLaunches method then returns an object that corresponds with the schema fields of the Launch schema type.

Let's refactor the getAllLaunches method to be a lot cleaner and concise.

...
launchReducer(launch) {
  return {
    id: launch.flight_number || 0,
    cursor: `${launch.flight_number || 0}-${launch.mission_name}`,
    mission: {
      name: launch.mission_name,
      patch: null, // what to do here?
    },
    year: launch.launch_year,
    date: launch.launch_date_unix,
    rocket: {
      id: launch.rocket.rocket_id,
      name: launch.rocket.rocket_name,
      type: launch.rocket.rocket_type,
    },
    launchSuccess: launch.launch_success,
  };
}

async getAllLaunches() {
  const res = await this.get('launches');

  return res && res.length ? res.map(l => this.launchReducer(l)) : [];
}

With the above changes, we can easily make changes to the launchReducer method while the getAllLaunches method stays lean and concise.

Now, let's take care of getting a specific launch. Add the following methods, getLaunchById, and getLaunchesByIds to the LaunchAPI class.

...
async getLaunchById({ launchId }) {
  const res = await this.get('launches', { flight_number: launchId });
  return this.launchReducer(res[0]);
}

async getLaunchesByIds({ launchIds }) {
  return Promise.all(
    launchIds.map(launchId => this.getLaunchById({ launchId })),
  );
}

The getLaunchById method takes in a flight number and returns the data for a particular launch, while getLaunchesByIds returns several launches based on their respective launchIds. Promise.all() takes an array of promises and returns a single promise that resolves when all the promises in the array have been resolved with their fulfilled values.

Connect a database