GithubHelp home page GithubHelp logo

apollosolutions / federation-subscription-tools Goto Github PK

View Code? Open in Web Editor NEW
104.0 12.0 34.0 130 KB

A set of demonstration utilities to facilitate GraphQL subscription usage alongside a federated data graph

License: MIT License

Dockerfile 4.79% JavaScript 41.16% HTML 6.18% CSS 10.82% TypeScript 37.05%
graphql apollo-federation subscriptions demo

federation-subscription-tools's Introduction

Using Subscriptions with a Federated Data Graph

Update June 2023: Federated subscriptions are now supported in GraphOS! You can now use subscriptions in your supergraph without these sidecar solutions.

This demonstration library shows how a decoupled subscription service can run alongside a federated data graph to provide real-time updates to a client. While the subscription service runs a separate non-federated Apollo Server, client applications do not need to perform any special handling of their subscription operations and may send those requests as they would to any GraphQL API that supports subscriptions. The subscription service's API may also specify return types for the Subscription fields that are defined in the federated data graph without explicitly redefining them in that service's type definitions.

In brief, the utilities contained within this library will allow you to:

  • Create a decoupled, independently scalable subscriptions service to run alongside a unified data graph
  • Use types defined in your unified data graph as return types within the subscriptions service's type definitions (without manually redefining those types in the subscriptions service)
  • Publish messages to a shared pub/sub implementation from any subgraph service
  • Allow clients to write subscription operations just as they would if the Subscription fields were defined directly within the unified data graph itself

Example Usage

The following section outlines how to use the utilities included with this library. The following code is based on a complete working example that has been included in the example directory of this repository. Please reference the full example code for additional implementation details and context.

Make an Executable Schema from Federated and Subscription Type Definitions

The subscriptions service should only contain a definition for the Subscription object type, the types on this field may output any of the types defined in the federated data graph's schema:

// typeDefs.js (subscriptions service)

import gql from "graphql-tag";

export const typeDefs = gql`
  type Subscription {
    postAdded: Post
  }
`;

To make the federated data graph's types available to the subscription service, instantiate an ApolloGateway and call the makeSubscriptionSchema function in the gateway's onSchemaLoadOrUpdate method to combine its schema with the subscription service's type definitions and resolvers to make the complete executable schema.

Managed federation option:

// index.js (subscriptions service)
let schema;
const gateway = new ApolloGateway();

gateway.onSchemaLoadOrUpdate(schemaContext => {
  schema = makeSubscriptionSchema({
    gatewaySchema: schemaContext.apiSchema,
    typeDefs,
    resolvers
  });
});

await gateway.load({ apollo: getGatewayApolloConfig(apolloKey, graphVariant) });

Unmanaged federation option:

// index.js (subscriptions service)
let schema;
const gateway = new ApolloGateway({
  serviceList: [
    /* Provide your service list here... */
  ],
  experimental_pollInterval = 36000;
});

gateway.onSchemaLoadOrUpdate(schemaContext => {
  schema = makeSubscriptionSchema({
    gatewaySchema: schemaContext.apiSchema,
    typeDefs,
    resolvers
  });
});

await gateway.load();

Note that for unmanaged federation, we must set a poll interval to query the subgraph services for their schemas to detect a schema change. Polling the running endpoint for these SDLs is fairly blunt approach, so in production, a more computationally efficient approach would be preferable (or managed federation).

Use an Apollo Data Source to Fetch Non-Payload Fields

The subscription service can resolve fields that are included in a published message's payload, but it will need to reach out to the federated data graph to resolve additional non-payload fields. Using an Apollo data source subclassed from the provided GatewayDataSource, specific methods can be defined that fetch the non-payload fields by diffing the payload fields with the overall selection set. Optionally, headers (etc.) may be attached to the request to the federated data graph by providing a willSendRequest method:

// LiveBlogDataSource/index.js (subscriptions service)

import { GatewayDataSource } from "federation-subscription-tools";
import gql from "graphql-tag";

export class LiveBlogDataSource extends GatewayDataSource {
  constructor(gatewayUrl) {
    super(gatewayUrl);
  }

  willSendRequest(request) {
    if (!request.headers) {
      request.headers = {};
    }

    request.headers["apollographql-client-name"] = "Subscriptions Service";
    request.headers["apollographql-client-version"] = "0.1.0";

    // Forwards the encoded token extracted from the `connectionParams` with
    // the request to the gateway
    request.headers.authorization = `Bearer ${this.context.token}`;
  }

  async fetchAndMergeNonPayloadPostData(postID, payload, info) {
    const selections = this.buildNonPayloadSelections(payload, info);
    const payloadData = Object.values(payload)[0];

    if (!selections) {
      return payloadData;
    }

    const Subscription_GetPost = gql`
      query Subscription_GetPost($id: ID!) {
        post(id: $id) {
          ${selections}
        }
      }
    `;

    try {
      const response = await this.query(Subscription_GetPost, {
        variables: { id: postID }
      });
      return this.mergeFieldData(payloadData, response.data.post);
    } catch (error) {
      console.error(error);
    }
  }
}

In the resolvers for the subscription field, the fetchAndMergeNonPayloadPostData method may be called to resolve all requested field data:

// resolvers.js (subscriptions service)

const resolvers = {
  Subscription: {
    postAdded: {
      resolve(payload, args, { dataSources: { gatewayApi } }, info) {
        return gatewayApi.fetchAndMergeNonPayloadPostData(
          payload.postAdded.id,
          payload, // known field values
          info // contains the complete field selection set to diff
        );
      },
      subscribe(_, args) {
        return pubsub.asyncIterator(["POST_ADDED"]);
      }
    }
  }
};

In effect, this means that as long the resource that is used as the output type for any subscriptions field may be queried from the federated data graph, then this node may be used as an entry point to that data graph to resolve non-payload fields.

For the gateway data source to be accessible in Subscription field resolvers, we must manually add it to the request context using the addGatewayDataSourceToSubscriptionContext function. Note that this example uses graphql-ws to serve the WebSocket-enabled endpoint for subscription operations. A sample implementation may be structured as follows:

// index.js (subscriptions service)

const httpServer = http.createServer(function weServeSocketsOnly(_, res) {
  res.writeHead(404);
  res.end();
});

const wsServer = new ws.Server({
  server: httpServer,
  path: "/graphql"
});

useServer(
  {
    execute,
    subscribe,
    context: ctx => {
      // If a token was sent for auth purposes, retrieve it here
      const { token } = ctx.connectionParams;

      // Instantiate and initialize the GatewayDataSource subclass
      // (data source methods will be accessible on the `gatewayApi` key)
      const liveBlogDataSource = new LiveBlogDataSource(gatewayEndpoint);
      const dataSourceContext = addGatewayDataSourceToSubscriptionContext(
        ctx,
        liveBlogDataSource
      );

      // Return the complete context for the request
      return { token: token || null, ...dataSourceContext };
    },
    onSubscribe: (_ctx, msg) => {
      // Construct the execution arguments
      const args = {
        schema,
        operationName: msg.payload.operationName,
        document: parse(msg.payload.query),
        variableValues: msg.payload.variables
      };

      const operationAST = getOperationAST(args.document, args.operationName);

      // Stops the subscription and sends an error message
      if (!operationAST) {
        return [new GraphQLError("Unable to identify operation")];
      }

      // Handle mutation and query requests
      if (operationAST.operation !== "subscription") {
        return [
          new GraphQLError("Only subscription operations are supported")
        ];
      }

      // Validate the operation document
      const errors = validate(args.schema, args.document);

      if (errors.length > 0) {
        return errors;
      }

      // Ready execution arguments
      return args;
    }
  },
  wsServer
);

httpServer.listen({ port }, () => {
  console.log(
    `๐Ÿš€ Subscriptions ready at ws://localhost:${port}${wsServer.options.path}`
  );
});

Try the Demo

Installation & Set-up

The full example code can be found in the example directory. To run the example, you'll need to create a new graph in Apollo Studio for the gateway, configure rover with your APOLLO_KEY, and then push the two services' schemas:

rover subgraph introspect http://localhost:4001 | rover subgraph publish blog@current --schema - --name authors --routing-url http://localhost:4001
rover subgraph introspect http://localhost:4002 | rover subgraph publish blog@current --schema - --name posts --routing-url http://localhost:4002

Important! The services for the authors and posts subgraphs will need to be running to fetch their schemas from the specified endpoints. You can quickly start up these services without the overhead of running a full docker-compose first by running npm run server:authors and npm run server:posts from the example/gateway-server directory (in two different terminal windows). Once the schemas have been successfully pushed to Apollo Studio, you can kill these processes.

Next, add .env files to the server and client directories:

  1. Add a .env file to the example/gateway-server directory using the example/gateway-server/.env.sample file as a template. Add your new APOLLO_KEY and APOLLO_GRAPH_REF as variables.
  2. Add a .env file to the example/subscriptions-server directory using the example/subscriptions-server/.env.sample file as a template. Add the same Apollo API key as the APOLLO_KEY and APOLLO_GRAPH_REF.
  3. Add a .env file to the example/client directory using the example/client/.env.sample file as a template.

Finally, run docker-compose up --build from the example directory to start all services.

TLDR;

cp example/gateway-server/.env.sample example/gateway-server/.env
cp example/subscriptions-server/.env.sample example/subscriptions-server/.env
cp example/client/.env.sample example/client/.env
docker-compose up --build

The federated data graph endpoint may be accessed at http://localhost:4000/graphql.

The subscriptions service WebSocket endpoint may be accessed at ws://localhost:5000/graphql.

A React app will be available at http://localhost:3000.

Usage

To see the post list in the client app update in real-time, add a new post at http://localhost:3000/post/add or run the following mutation directly:

mutation AddPost {
  addPost(authorID: 1, content: "Hello, world!", title: "My Next Post") {
    id
    author {
      name
    }
    content
    publishedAt
    title
  }
}

Rationale

The architecture demonstrated in this project seeks to provide a bridge to native Subscription operation support in Apollo Federation. This approach to subscriptions has the advantage of allowing the Apollo Gateway API to remain as the "stateless execution engine" of a federated data graph while offloading all subscription requests to a separate service, thus allowing the subscription service to be scaled independently of the gateway.

To allow the Subscription fields to specify return types that are defined in gateway API only, the federated data graph's type definitions are merged with the subscription service's type definitions and resolvers in the gateway's onSchemaChange callback to avoid re-declaring these types explicitly here.

Architectural Details

Components

Docker will start five different services with docker-compose up:

1. Gateway Server + Subgraph Services

This service contains the federated data graph. For simplicity's sake, two implementing services (for authors and posts) have been bundled with the gateway API in this service. Each implementing service connects to Redis as needed so it can publish events from mutations (the "pub" end of subscriptions). For example:

import { pubsub } from "./redis";

export const resolvers = {
  // ...
  Mutation: {
    addPost(root, args, context, info) {
      const post = newPost();
      pubsub.publish("POST_ADDED", { postAdded: post });
      return post;
    }
  }
};

2. Subscriptions Server

This service also connects to Redis to facilitate the "sub" end of the subscriptions. This service is where the Subscription type and related fields are defined. As a best practice, only define a Subscription type and applicable resolvers in this service.

When sending subscription data to clients, the subscription service can't automatically resolve any data beyond what's provided in the published payload from the implementing service. This means that to resolve nested types (or any other fields that aren't immediately available in the payload object), the resolvers must be defined in the subscription services to fetch this data on a field-by-field basis.

There are a number of possible approaches that could be taken here, but one recommended approach is to provide an Apollo data source with methods that automatically compare the fields included in the payload against the fields requested in the operation, then selectively query the necessary field data in a single request to the gateway, and finally combine the returned data with the with original payload data to fully resolve the request. For example:

import { pubsub } from "./redis";

export const resolvers = {
  Subscription: {
    postAdded: {
      resolve(payload, args, { dataSources: { gatewayApi } }, info) {
        return gatewayApi.fetchAndMergeNonPayloadPostData(
          payload.postAdded.id,
          payload,
          info
        );
      },
      subscribe(_, args) {
        return pubsub.asyncIterator(["POST_ADDED"]);
      }
    }
  }
};

3. Redis

A shared Redis instance is used to capture publications from the services behind the federated data graph as well as the subscriptions initiated in the subscriptions service, though other PubSub implementations could easily be supported. Note that an in-memory pub/sub implementation will not work because it cannot be shared between the separate gateway and subscription services.

4. React App

The React app contains a homepage with a list of posts as well as a form to add new posts. When a new post is added, the feed of posts on the homepage will be automatically updated.

Diagram

The architecture of the provided example may be visualized as follows:

Architectural diagram of a federated data graph with a subscriptions service and a React client app

Important Considerations

Subscriptions Must be Defined in a Single Service:

This solution requires all Subscription fields to be defined in a single, decoupled subscription service. This requirement may necessitate that ownership of this service is shared amongst teams that otherwise manage independent portions of the schema applicable to queries and mutations.

Synchronizing Event Labels:

Some level of coordination would be necessary to ensure that event labels (e.g. POST_ADDED) are synchronized between the implementing services that publish events and the subscription service that calls the asyncIterator method with these labels as arguments. Breaking changes may occur without such coordination.

federation-subscription-tools's People

Contributors

aevaldas avatar burn2delete avatar mandiwise avatar smyrick avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

federation-subscription-tools's Issues

How would you adapt it for apollo router?

For example, makeSubscriptionSchema function takes in gatewaySchema: GraphQLSchema. How should I create gatewaySchema schema from supergraph.graphql?

Is there a much better way to handle subscription now?

"Expected undefined to be a GraphQL schema"

I've encountered this problem in react-app + subscriptions-service running in unmanaged mode
When it tries to establish ws connection, it seems to fail with 1011 error code from graphql-ws,
"Expected undefined to be a GraphQL schema.".. Apparently subscriptions-service doesn't have valid schema

Looks like on backend, inside onSubscribe, schema is undefined

Screenshot 2021-07-19 at 21 53 03

Screenshot 2021-07-19 at 23 46 15

After some debugging on backend I got to this error
An error was thrown from an 'onSchemaChange' listener. The schema will still update: Cannot use GraphQLNonNull "Int!" from another module or realm.

Likely caused by duplicate graphql package in federation-subscription-tools folder compared to subscriptions-server
But I suspect this is because of my meddling with graphql in #6
Screenshot 2021-07-20 at 01 01 07

Unable to use library in a typescript project

I think because this:

import { createHash } from "crypto";

const { gql, makeExecutableSchema } = require("apollo-server");
const { printSchema } = require("graphql")

./src/utils/schema.js

WebSocket protocol error occured. It was most likely caused due to an unsupported subprotocol "graphql-transport-ws" requested by the client. graphql-ws implements exclusively the "graphql-transport-ws" subprotocol, please make sure that the client implements it too.

Hi!. I tried the Demo code but i'm getting this message in the logs of the subscriptions-server:

WebSocket protocol error occured. It was most likely caused due to an unsupported subprotocol "graphql-transport-ws" requested by the client. graphql-ws implements exclusively the "graphql-transport-ws" subprotocol, please make sure that the client implements it too.

The react-app connect successfully to the gateway but is disconnected from subscriptions-server
Debugging inside Chrome DevTools -> Networking -> WS
This is the data showed:

1.- {"type":"connection_init","payload":{"token":"some-token"}}

2.- {"type":"connection_ack"}

3.- {"id":"b44837e1-7467-4f69-a190-bcf6c7d24099","type":"subscribe","payload":{"variables":{},"extensions":{},"operationName":"PostAdded","query":"subscription PostAdded {\n postAdded {\n ...PostFields\n __typename\n }\n}\n\nfragment PostFields on Post {\n author {\n id\n name\n __typename\n }\n content\n id\n publishedAt\n title\n __typename\n}\n"}}

4.- Invalid frame header

Any suggestion of what I'm doing wrong. I'm using the code inside the example folder without any modifications.
Thanks!!!

Subscription with fragments failing

Given a subscription with the shape:

subscription($myId: ID!) {
  myQuery(id: $myId) {
    id
    fieldA {
      filedB {
        ... on UnionTypeA {
          fieldC
          fieldD
        }
        ... on UnionTypeB {
          fieldC
          fieldE
        }
      }
    }
  }
}

My subscription gateway is generating errors from the backend:

[GraphQL error]: Cannot query field "fieldC" on type "MyUnion". Did you mean to use an inline fragment on "UnionTypeA" or "UnionTypeB"?
[GraphQL error]: Cannot query field "fieldD" on type "MyUnion". Did you mean to use an inline fragment on "UnionTypeA" or "UnionTypeB"?
[GraphQL error]: Cannot query field "fieldE" on type "MyUnion". Did you mean to use an inline fragment on "UnionTypeA" or "UnionTypeB"?

Additionally, logging the selection being returned from the buildNonPayloadSelections method is giving the following shape:

id  fieldA { fieldB { fieldC } } fieldA { fieldB { fieldD } } fieldA { fieldB { fieldE } }

It seems the generated selection has dropped the fragments?

GatewayDataSource is missing import(s)

referenceError: ApolloError is not defined
at LiveBlogDataSource.resolveUri (/federation-subscription-tools/example/subscriptions-server/node_modules/federation-subscription-tools/src/datasources/GatewayDataSource/index.js:73:7)

Update graphql lib version

Starting from ^2.0.0 version ApolloGateway uses graphql ^16.0.0 version as a dependency.
It produces a version conflict error since federation-subscription-tools still using graphql version 15.

Error: Cannot use GraphQLInputObjectType "{ ...schema }" from another module or realm.

Ensure that there is only one instance of "graphql" in the node_modules
directory. If different versions of "graphql" are the dependencies of other
relied on modules, use "resolutions" to ensure only one version is installed.

Can you update the dependency?

fieldPathsAsStrings potential issue?

In https://github.com/apollosolutions/federation-subscription-tools/blob/main/src/datasources/GatewayDataSource/index.js you have a function fieldPathsAsStrings.

I may misunderstood the intention of this function, however it appears to me that line 129 should replace the occurrence of key with fullPath, otherwise, if that function was given an input like so:
fieldPathsAsString({ a: { a: { a: '' } } })
The result would be:
['a', 'a', 'a.a.a']
Whereas I believe the expectation is for that input to return:
['a', 'a.a', 'a.a.a']?

Please let me know if my assumption is correct and I can open a PR, cheers

explanation of federation-subscription-tools

Hi, I'd like to use subscriptions for my federation server, I came across this repo, and noticed that federation-subscription-tools is published, but the last version on npm is from a year ago.

are you guys maintaining this library? or is it a package to mostly show off the possibility of subscriptions using the tools you provide already?

Error "CheckFailed: one or more checks failed"

I cloned the repo, setup .env files, uploaded subgraph schemas, and when I run docker-compose up --build I got this error:

subscriptions_server  | This package has been deprecated and now it only exports makeExecutableSchema.
subscriptions_server  | And it will no longer receive updates.
subscriptions_server  | We recommend you to migrate to scoped packages such as @graphql-tools/schema, @graphql-tools/utils and etc.
subscriptions_server  | Check out https://www.graphql-tools.com to learn what package you should use instead!
gateway_server        | [server:gateway] Gateway successfully initialized (but not yet loaded)
gateway_server        | [server:gateway] Loading gateway...
subscriptions_server  | Gateway successfully initialized (but not yet loaded)
subscriptions_server  | Loading gateway...
gateway_server        | [server:gateway] (node:125) UnhandledPromiseRejectionWarning: CheckFailed: one or more checks failed
gateway_server        | [server:gateway]     at new GraphQLErrorExt (/home/node/app/node_modules/@apollo/core-schema/dist/error.js:7:9)
gateway_server        | [server:gateway]     at err (/home/node/app/node_modules/@apollo/core-schema/dist/error.js:39:19)
gateway_server        | [server:gateway]     at ErrCheckFailed (/home/node/app/node_modules/@apollo/core-schema/dist/core.js:15:52)
gateway_server        | [server:gateway]     at CoreSchema.check (/home/node/app/node_modules/@apollo/core-schema/dist/core.js:72:42)
gateway_server        | [server:gateway]     at CoreSchema.check (/home/node/app/node_modules/@apollo/core-schema/dist/schema.js:71:22)
gateway_server        | [server:gateway]     at ApolloGateway.createSchemaFromSupergraphSdl (/home/node/app/node_modules/@apollo/gateway/dist/index.js:500:14)
gateway_server        | [server:gateway]     at ApolloGateway.updateWithSupergraphSdl (/home/node/app/node_modules/@apollo/gateway/dist/index.js:422:72)
gateway_server        | [server:gateway]     at ApolloGateway.externalSupergraphUpdateCallback (/home/node/app/node_modules/@apollo/gateway/dist/index.js:377:18)
gateway_server        | [server:gateway]     at ApolloGateway.initializeSupergraphManager (/home/node/app/node_modules/@apollo/gateway/dist/index.js:350:18)
gateway_server        | [server:gateway]     at processTicksAndRejections (internal/process/task_queues.js:93:5)
gateway_server        | [server:gateway] (Use `node --trace-warnings ...` to show where the warning was created)
gateway_server        | [server:gateway] (node:125) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
gateway_server        | [server:gateway] (node:125) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
gateway_server        | [server:gateway] [nodemon] clean exit - waiting for changes before restart
subscriptions_server  | (node:30) UnhandledPromiseRejectionWarning: TypeError: Cannot assign to read only property 'name' of object 'GraphQLError: one or more checks failed'
subscriptions_server  |     at new GraphQLErrorExt (/home/node/app/node_modules/@apollo/core-schema/dist/error.js:15:19)
subscriptions_server  |     at err (/home/node/app/node_modules/@apollo/core-schema/dist/error.js:39:19)
subscriptions_server  |     at ErrCheckFailed (/home/node/app/node_modules/@apollo/core-schema/dist/core.js:15:52)
subscriptions_server  |     at CoreSchema.check (/home/node/app/node_modules/@apollo/core-schema/dist/core.js:72:42)
subscriptions_server  |     at CoreSchema.check (/home/node/app/node_modules/@apollo/core-schema/dist/schema.js:71:22)
subscriptions_server  |     at ApolloGateway.createSchemaFromSupergraphSdl (/home/node/app/node_modules/@apollo/gateway/dist/index.js:500:14)
subscriptions_server  |     at ApolloGateway.updateWithSupergraphSdl (/home/node/app/node_modules/@apollo/gateway/dist/index.js:422:72)
subscriptions_server  |     at ApolloGateway.externalSupergraphUpdateCallback (/home/node/app/node_modules/@apollo/gateway/dist/index.js:377:18)
subscriptions_server  |     at ApolloGateway.initializeSupergraphManager (/home/node/app/node_modules/@apollo/gateway/dist/index.js:350:18)
subscriptions_server  |     at processTicksAndRejections (internal/process/task_queues.js:93:5)
subscriptions_server  | (Use `node --trace-warnings ...` to show where the warning was created)
subscriptions_server  | (node:30) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
subscriptions_server  | (node:30) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
react_app             | Compiled successfully!

Does anyone know what's the cause of this. Thank you.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.