Hi, Apollo-local-query looks to be a great package for me to avoid go out and back through the networking stack, before I'm looking to gain some speed to render the data of my queries.
Unfortunately, I'm facing some troubles to install it in my context:
After doing the installation as presented in the readme, I'm getting the following error, which says that the dependencies related to the Koa server:
ERROR Failed to compile with 18 errors
These modules were not found:
* child_process in ./node_modules/stripe/lib/stripe.js, ./node_modules/os-locale/index.js
* net in ./node_modules/forever-agent/index.js, ./node_modules/tunnel-agent/index.js and 2 others
* tls in ./node_modules/forever-agent/index.js, ./node_modules/tunnel-agent/index.js and 1 other
* module in ./node_modules/require_optional/node_modules/resolve-from/index.js
* fs in ./node_modules/nconf/lib/nconf.js, ./node_modules/y18n/index.js and 6 others
To install them, you can run: npm install --save child_process net tls module fs
My working code before the installation was looking like this:
import { ApolloClient, createNetworkInterface } from 'react-apollo';
import fetch from 'isomorphic-fetch';
let apolloClient = null;
// Polyfill fetch() on the server (used by apollo-client)
if (!process.browser) {
global.fetch = fetch;
}
function create() {
return new ApolloClient({
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
networkInterface: createNetworkInterface({
uri: '/graphql',
opts: {
credentials: 'include'
}
})
});
}
export default function initApollo() {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (!process.browser) {
return create();
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = create();
}
return apolloClient;
}
After the installation of apollo-local-query, it was looking like this:
import { ApolloClient, createNetworkInterface } from 'react-apollo';
// >>I've tried with it too import fetch from 'isomorphic-fetch';
import { createLocalInterface } from 'apollo-local-query';
import * as graphql from 'graphql'; >> // I've also tried with: import graphql from 'graphql';
import schema from '../../server/graphql/schema';
const isServer = !process.browser;
const options = { credentials: 'include' };
if (isServer) {
options.networkInterface = createLocalInterface(graphql, schema);
options.ssrMode = true;
}
const myClient = new ApolloClient(options)
export default function initApollo() {
return myClient;
}
For information, my schema.js is looking like this:
const { makeExecutableSchema } = require('graphql-tools');
const resolvers = require('./resolvers');
const Schemas = require('./schemas');
const { User, Service } = require('./types');
const Queries = `
type Query {
${User.queries}
${Service.queries}
}
`;
const Mutations = `
type Mutation {
${User.mutations}
${Service.mutations}
}
`;
const Scalars = `
scalar Date
`;
const Inputs = `
input TranslationInput {
en: String
es: String
ca: String
fr: String
}
// ... With others inputs
`;
module.exports = makeExecutableSchema({
typeDefs: [Queries, Mutations, Scalars, Inputs, ...Schemas],
resolvers
});
The file where I call the "initApollo" function to pass the ApolloClient to the Apollo Provider is looking like this:
import React from 'react'
import PropTypes from 'prop-types'
import { ApolloProvider, getDataFromTree } from 'react-apollo'
import Head from 'next/head'
import initApollo from './apollo'
import initRedux from './store'
// Gets the display name of a JSX component for dev tools
function getComponentDisplayName (Component) {
return Component.displayName || Component.name || 'Unknown'
}
export default ComposedComponent => {
return class WithData extends React.Component {
static displayName = `WithData(${getComponentDisplayName(ComposedComponent)})`
static propTypes = {
serverState: PropTypes.object.isRequired
}
static async getInitialProps (ctx) {
let serverState = {}
// Evaluate the composed component's getInitialProps()
let composedInitialProps = {}
if (ComposedComponent.getInitialProps) {
composedInitialProps = await ComposedComponent.getInitialProps(ctx)
}
// Run all GraphQL queries in the component tree
// and extract the resulting data
if (!process.browser) {
const apollo = initApollo()
const redux = initRedux(apollo, { test: { also: 'this' } })
// Provide the `url` prop data in case a GraphQL query uses it
const url = {query: ctx.query, pathname: ctx.pathname}
try {
// Run all GraphQL queries
await getDataFromTree(
// No need to use the Redux Provider
// because Apollo sets up the store for us
<ApolloProvider client={apollo} store={redux}>
<ComposedComponent url={url} {...composedInitialProps} />
</ApolloProvider>
)
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
// Extract query data from the store
const state = redux.getState()
// No need to include other initial Redux state because when it
// initialises on the client-side it'll create it again anyway
serverState = {
apollo: { // Only include the Apollo data state
data: state.apollo.data
}
}
}
return {
serverState,
...composedInitialProps
}
}
constructor (props) {
super(props)
this.apollo = initApollo()
this.redux = initRedux(this.apollo, this.props.serverState)
}
render () {
return (
// No need to use the Redux Provider
// because Apollo sets up the store for us
<ApolloProvider client={this.apollo} store={this.redux}>
<ComposedComponent {...this.props} />
</ApolloProvider>
)
}
}
}
And my server.js with Koa is looking like this:
const IntlPolyfill = require('intl');
const Koa = require('koa');
const convert = require('koa-convert');
const router = require('./routes.js');
const session = require('koa-generic-session');
const passport = require('koa-passport');
const bodyParser = require('koa-bodyparser');
const accepts = require('accepts');
const next = require('next');
const { graphqlKoa, graphiqlKoa } = require('graphql-server-koa');
const { basename } = require('path');
const glob = require('glob');
const { readFileSync } = require('fs');
const en = require('../helpers/intl/messages/en.js');
const es = require('../helpers/intl/messages/en.js');
const ca = require('../helpers/intl/messages/en.js');
const fr = require('../helpers/intl/messages/en.js');
const messages = { en, es, ca, fr };
// const compression = require('compression');
require('./auth');
const { Company } = require('./models');
const schema = require('./graphql/schema');
const nconf = require('../env/nconf');
Intl.NumberFormat = IntlPolyfill.NumberFormat;
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
const port = process.env.PORT || nconf.get('PORT');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const languages = glob.sync('./helpers/intl/messages/*.js').map(f => basename(f, '.js'));
const localeDataCache = new Map();
const getLocaleDataScript = (locale) => {
// const lang = locale.split('-')[0]
const lang = locale;
if (!localeDataCache.has(lang)) {
const localeDataFile = require.resolve(`react-intl/locale-data/${lang}`);
const localeDataScript = readFileSync(localeDataFile, 'utf8');
localeDataCache.set(lang, localeDataScript);
}
return localeDataCache.get(lang);
};
const getMessages = (locale, companyMessages) =>
Object.assign({}, messages[locale], companyMessages);
app.keys = [nconf.get('APP_KEYS')];
app.prepare()
.then(() => {
const server = new Koa();
server
.use(bodyParser())
.use(convert(session()))
.use(passport.initialize())
.use(passport.session())
.use(router.routes())
.use(router.allowedMethods());
// .use(compression())
router.post('/graphql', graphqlKoa(async (ctx) => {
const host = ctx.req.headers.host;
const company = await Company.findOne({ website_url: host.replace('www.', '') });
return ({
schema,
context: {
ctx,
user: ctx.state.user,
company
}
});
}));
router.get('/graphiql', graphiqlKoa({ endpointURL: '/graphql' }));
router.get('*', async (ctx) => {
const host = ctx.req.headers.host;
const accept = accepts(ctx.req);
const locale = accept.language(languages);
const company = await Company.findOne({ website_url: host.replace('www.', '') });
ctx.req.company = company;
ctx.req.locale = locale;
ctx.req.localeDataScript = getLocaleDataScript(locale);
ctx.req.messages = getMessages(locale, company.website[locale]);
await handle(ctx.req, ctx.res);
ctx.respond = false;
});
server.listen(port, (err) => {
if (err) throw err;
console.log(`> Ready on http://localhost:${port}`);
});
});
Does anyone have an idea how to fix this issue?
If someone succeed to give me some hint about how to fix it, I'm proposing myself to produce some extra documentation for apollo-local-query to illustrate the setup for this context! ;)
Thank for your help!