API Routes on Next.JS with Apollo-Server : why does using a basePath cause error? - graphql

I'm using API Routes on a Next.JS app with Apollo-Server, to create a GraphQL API wrapper around a REST endpoint. But I'm running into an issue if the project has a bathPath.
I've been following this example, and a video tutorial on youtube.
I've replicated my issue by using the repo from that tutorial. Upon running that code $ yarn dev, and navigating to http://localhost:3000/api/graphql the GraphQl playground shows up, and works as expected.
However if I add a basepath to the project then the graphQl playground still shows up fine at http://localhost:300/basepath/api/graphql but it gives me the error of "Server cannot be reached" and shows a 404 error in the network tab on the dev tools.
To add the base path I created a next.config.js and added
module.exports = {
basePath: '/basepath'
}
In pages/api/graphql.ts I updated the path from /api/graphql to /basepath/api/graphql
import { ApolloServer } from "apollo-server-micro";
import { schema } from "src/schema";
const server = new ApolloServer({ schema });
const handler = server.createHandler({ path: "/somewhere/api/graphql" });
export const config = {
api: {
bodyParser: false,
},
};
export default handler;
In src/apollo.ts I updated the HttpLink uri from /api/graphql to /basepath/api/graphql
import {
ApolloClient,
InMemoryCache,
NormalizedCacheObject,
} from "#apollo/client";
import { useMemo } from "react";
let apolloClient: ApolloClient<NormalizedCacheObject>;
function createIsomorphicLink() {
if (typeof window === "undefined") {
// server
const { SchemaLink } = require("#apollo/client/link/schema");
const { schema } = require("./schema");
return new SchemaLink({ schema });
} else {
// client
const { HttpLink } = require("#apollo/client/link/http");
return new HttpLink({ uri: "/bathpath/api/graphql" });
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: createIsomorphicLink(),
cache: new InMemoryCache(),
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
if (initialState) {
_apolloClient.cache.restore(initialState);
}
if (typeof window === "undefined") return _apolloClient;
apolloClient = apolloClient ?? _apolloClient;
return apolloClient;
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}
Any ideas why adding a basepath would break this setup? and what I'd need to do to fix it?
This is my first time posting on stack overflow, so I hope my description is good enough, please do ask if I've missed anything and thanks for your help in advance!

Related

Apollo GraphQL: GraphQLWsLink (Subscriptions) Troubles. Cannot get WebSocket implementation to work w/ Next.js

So I have a GraphQL server that I wrote in Go, following this tutorial pretty closely. I have my front-end written as a Next.js application, and I am currently trying to create a client to connect to my server and even following the subscription docs to the T, I cannot seem to get it to work. How is it that the examples provided do not include a webSocketImpl?
If I don't provide a webSocketImpl, I get this:
Error: WebSocket implementation missing; on Node you can `import WebSocket from 'ws';` and pass `webSocketImpl: WebSocket` to `createClient`
So, naturally, I import { WebSocket } from "ws"; , and have:
const wsLink = new GraphQLWsLink(
createClient({
webSocketImpl: WebSocket,
url: "ws://localhost:8080/subscriptions",
})
);
Where I then get:
error - ./node_modules/node-gyp-build/index.js:1:0
Module not found: Can't resolve 'fs'
Here is the full code, basically all I need is to create a ApolloClient and export it for use in my React code.
import { ApolloClient, HttpLink, InMemoryCache, split } from "#apollo/client";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
import { getMainDefinition } from "#apollo/client/utilities";
import { WebSocket } from "ws";
const wsLink = new GraphQLWsLink(
createClient({
webSocketImpl: WebSocket,
url: "ws://localhost:8080/subscriptions",
})
);
const httpLink = new HttpLink({
uri: `http://localhost:8080/query`,
});
const link = split(
({ query }) => {
const def = getMainDefinition(query);
return (
def.kind === "OperationDefinition" && def.operation === "subscription"
);
},
wsLink,
httpLink
);
export const Client = new ApolloClient({
link,
cache: new InMemoryCache(),
});
Am I totally missing something here? Is there not a default WebSocket implementation in my installation? Obviously the "ws" implementation isn't cutting it, probably because fs is not available in-browser?
A major thing I left off here: I was using Next.js. The reason this was occurring was due to SSR. Basically, we need to only generate the WebSocket link if we are in the browser by using `typeof window !== 'undefined'. This is my updated code:
import { ApolloClient, HttpLink, InMemoryCache, split } from "#apollo/client";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
import { getMainDefinition } from "#apollo/client/utilities";
const wsLink =
typeof window !== "undefined"
? new GraphQLWsLink(
createClient({
url: "ws://localhost:8080/subscriptions",
})
)
: null;
const httpLink = new HttpLink({
uri: `http://localhost:8080/query`,
});
const link =
typeof window !== "undefined" && wsLink != null
? split(
({ query }) => {
const def = getMainDefinition(query);
return (
def.kind === "OperationDefinition" &&
def.operation === "subscription"
);
},
wsLink,
httpLink
)
: httpLink;
export const client = new ApolloClient({
link,
cache: new InMemoryCache(),
});
I found a way how it`s work with GraphQL-yoga
It's client :
// import { createServer } from "#graphql-yoga/node";
import { makeExecutableSchema } from "#graphql-tools/schema";
import typeDefs from "#/server/graphql/typeDef/schema.graphql";
import resolvers from "#/server/graphql/resolvers";
import dbInit from "#/lib/dbInit";
import JWT from "jsonwebtoken";
import Cors from "micro-cors";
// const pubsub = new PubSub();
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
import {
createServer,
createPubSub,
GraphQLYogaError,
} from "#graphql-yoga/node";
import { useResponseCache } from "#envelop/response-cache";
import { WebSocketServer } from "ws"; // yarn add ws
// import ws from 'ws'; yarn add ws#7
// const WebSocketServer = ws.Server;
import { useServer } from "graphql-ws/lib/use/ws";
const pubSub = createPubSub();
const server = createServer({
cors: {
credentials: "same-origin",
origin: ["http://localhost:3000"], // your frontend url.
},
plugins: [
useResponseCache({
includeExtensionMetadata: true,
}),
],
context: async (ctx) => {
let wsServer = null;
wsServer = ctx.res.socket.server.ws ||= new WebSocketServer({
port: 4000,
path: "/api/graphql",
});
wsServer &&= useServer({ schema }, wsServer);
const db = await dbInit();
let { token, customerId, customerExpire } = ctx.req.cookies;
// 1. Find optional visitor id
let id = null;
if (token) {
try {
let obj = JWT.verify(token, "MY_SECRET");
id = obj.id;
} catch (err) {
console.error("error on apollo server", err); // expired token, invalid token
// TODO try apollo-link-error on the client
throw new AuthenticationError(
"Authentication token is invalid, please log in"
);
}
}
return {
...ctx,
userId: id,
customerId,
pubSub,
};
},
schema,
});
export default server;
And client
import { useMemo } from "react";
import {
ApolloClient,
InMemoryCache,
split,
HttpLink,
createHttpLink,
} from "#apollo/client";
import merge from "deepmerge";
import { getMainDefinition } from "apollo-utilities";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
// const link = process.env.SERVER_LINK;
let apolloClient;
//create websocket link
const wsLink =
typeof window !== "undefined"
? new GraphQLWsLink(
createClient({
url: "ws://localhost:4000/api/graphql",
on: {
connected: () => console.log("connected client"),
closed: () => console.log("closed"),
},
})
)
: null;
//create http link
const httplink = new HttpLink({
uri: "http://localhost:3000/api/graphql",
credentials: "same-origin",
});
//Split the link based on graphql operation
const link =
typeof window !== "undefined"
? split(
//only create the split in the browser
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === "OperationDefinition" && operation === "subscription";
},
wsLink,
httplink
)
: httplink;
//create apollo client
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: link,
cache: new InMemoryCache(),
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract();
// Merge the existing cache into data passed from getStaticProps/getServerSideProps
const data = merge(initialState, existingCache);
// Restore the cache with the merged data
_apolloClient.cache.restore(data);
}
if (typeof window === "undefined") return _apolloClient;
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}

Graphql Server cannot be reached in nextjs production

i tested it in locally it work perfectly to fetch data from graphql. But, it up to production version the playground show message Server cannot be reached. What's the different in my locally and production server?
i setup server manually in digitalOcean. It is in nginx and pm2 to start the web.
i had install the mongdb in my server.
this project created from nextjs framework
Below that is my ApolloServer Code:
import { ApolloServer } from "apollo-server-micro";
import { MongoClient } from "mongodb";
import { schema } from "#/apollo/schema";
let db;
const apolloServer = new ApolloServer({
schema,
playground: true,
context: async ({ res, req }) => {
if (!db) {
try {
const dbClient = new MongoClient(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
if (!dbClient.isConnected()) await dbClient.connect();
db = dbClient.db("databaseName");
} catch (error) {
console.log(`error while connecting with graphql context (db):`;
}
}
return { db, res, req };
},
});
export const config = {
api: {
bodyParser: false,
},
};
export default apolloServer.createHandler({ path: "/api/graphql" });
Updated: Solved
Is the uri wrong? that is my website url.
return new ApolloClient({
ssrMode,
link: new HttpLink({
uri: `http://dayfruit.staging.domian.com/api/graphql`,
credentials: "include",
fetch,
}),
cache,
});

Apollo Server + Next.js - GraphQL Schema Not Updating

I have an Apollo GraphQL / Next.js application. After changing my graphql schema and navigating to the graphql playground at "http://localhost:3000/api/graphql", the old schema is still being referenced in the playground and in my application.
I've tried clearing node modules and running npm install, clearing cache, restarting everything, and I just can't wrap my head around why my schema is not updating. Am I missing some crucial schema-update step?
Here is my schema for Series and Publisher (note that a SeriesInput requires a Publisher, NOT a PublisherInput):
type Series {
_id: ID!
name: String
altID: String
publisher: Publisher!
comics: [Comic]
}
input SeriesInput {
_id: ID
name: String!
altID: String
publisher: Publisher!
comics: [Comic]
}
type Mutation {
addSeries(series: SeriesInput): Series
}
type Query {
series: [Series]
}
-------------------------
type Publisher {
_id: ID!
name: String
altID: String
series: [Series]
}
input PublisherInput {
_id: ID!
name: String!
altID: String
series: [Series]
}
type Mutation {
addPublisher(publisher: PublisherInput): Publisher
}
type Query {
publishers: [Publisher]
}
Here is the error message I am getting in GraphQL Playground which is due to the fact that the old series schema requires a PublisherInput type which has a mandatory field of "Name" which I am not passing.
Here is my graphql apollo server code where I am using mergeResolvers and mergeTypeDefs to merge all of the graphql files into a single schema:
import { ApolloServer } from "apollo-server-micro";
import { mergeResolvers, mergeTypeDefs } from "graphql-tools";
import connectDb from "../../lib/mongoose";
// Mutations and resolvers
import { comicsResolvers } from "../../api/comics/resolvers";
import { comicsMutations } from "../../api/comics/mutations";
import { seriesResolvers } from "../../api/series/resolvers";
import { seriesMutations } from "../../api/series/mutations";
import { publishersResolvers } from "../../api/publishers/resolvers";
import { publishersMutations } from "../../api/publishers/mutations";
// GraphQL Schema
import Publishers from "../../api/publishers/Publishers.graphql";
import Series from "../../api/series/Series.graphql";
import Comics from "../../api/comics/Comics.graphql";
// Merge type resolvers, mutations, and type definitions
const resolvers = mergeResolvers([
publishersMutations,
publishersResolvers,
seriesMutations,
seriesResolvers,
comicsMutations,
comicsResolvers,
]);
const typeDefs = mergeTypeDefs([Publishers, Series, Comics]);
// Create apollo server and connect db
const apolloServer = new ApolloServer({ typeDefs, resolvers });
export const config = {
api: {
bodyParser: false,
},
};
const server = apolloServer.createHandler({ path: "/api/graphql" });
export default connectDb(server);
Here is my apollo/next.js code which I used from Vercel's documentation:
* Code copied from Official Next.js documentation to work with Apollo.js
* https://github.com/vercel/next.js/blob/6e77c071c7285ebe9998b56dbc1c76aaf67b6d2f/examples/with-apollo/lib/apollo.js
*/
import React, { useMemo } from "react";
import Head from "next/head";
import { ApolloProvider } from "#apollo/react-hooks";
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from "apollo-link-http";
import fetch from "isomorphic-unfetch";
let apolloClient = null;
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
* #param {Function|Class} PageComponent
* #param {Object} [config]
* #param {Boolean} [config.ssr=true]
*/
export function withApollo(PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = useMemo(() => apolloClient || initApolloClient(apolloState), []);
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
// Set the correct displayName in development
if (process.env.NODE_ENV !== "production") {
const displayName = PageComponent.displayName || PageComponent.name || "Component";
if (displayName === "App") {
console.warn("This withApollo HOC only works with PageComponents.");
}
WithApollo.displayName = `withApollo(${displayName})`;
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async (ctx) => {
const { AppTree } = ctx;
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApolloClient());
// Run wrapped getInitialProps methods
let pageProps = {};
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx);
}
// Only on the server:
if (typeof window === "undefined") {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps;
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import("#apollo/react-ssr");
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient,
}}
/>
);
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error("Error while running `getDataFromTree`", error);
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
}
// Extract query data from the Apollo store
const apolloState = apolloClient.cache.extract();
return {
...pageProps,
apolloState,
};
};
}
return WithApollo;
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* #param {Object} initialState
*/
function initApolloClient(initialState) {
// 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 (typeof window === "undefined") {
return createApolloClient(initialState);
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(initialState);
}
return apolloClient;
}
/**
* Creates and configures the ApolloClient
* #param {Object} [initialState={}]
*/
function createApolloClient(initialState = {}) {
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
return new ApolloClient({
ssrMode: typeof window === "undefined", // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
uri: "http://localhost:3000/api/graphql", // Server URL (must be absolute)
credentials: "same-origin", // Additional fetch() options like `credentials` or `headers`
fetch,
}),
cache: new InMemoryCache().restore(initialState),
});
}
I ran into the same problem and the reason that's happening is the way next js handles caching. Delete the .next folder then restart your server, that will solve the issue.
Well I spent almost 5 days trying to figure out what I did wrong or if Apollo server is caching the schema on production any to found the Apollo client origin is pointing to the wrong server.
Maybe you should check well as well.

Apollo client QUERIES not sending headers to server but mutations are fine

I hooked up a front end to a graphql server. Most if not all the mutations are protected while all the queries are not protected. I have an auth system in place where if you log in, you get an access/refresh token which all mutations are required to use. And they do which is great, backend receives the headers and everything!
HOWEVER. There is one query that needs at least the access token to distinguish the current user! BUT the backend does not receive the two headers! I thought that the middlewareLink I created would be for all queries/mutations but I'm wrong and couldn't find any additional resources to help me out.
So here's my setup
apollo-client.js
import { InMemoryCache } from "apollo-cache-inmemory"
import { persistCache } from "apollo-cache-persist"
import { ApolloLink } from "apollo-link"
import { HttpLink } from "apollo-link-http"
import { onError } from "apollo-link-error"
import { setContext } from "apollo-link-context"
if (process.browser) {
try {
persistCache({
cache,
storage: window.localStorage
})
} catch (error) {
console.error("Error restoring Apollo cache", error)
}
}
const httpLink = new HttpLink({
uri: process.env.GRAPHQL_URL || "http://localhost:4000/graphql"
})
const authMiddlewareLink = setContext(() => ({
headers: {
authorization: localStorage.getItem("apollo-token") || null,
"x-refresh-token": localStorage.getItem("refresh-token") || null
}
}))
const afterwareLink = new ApolloLink((operation, forward) =>
forward(operation).map(response => {
const context = operation.getContext()
const {
response: { headers }
} = context
if (headers) {
const token = headers.get("authorization")
const refreshToken = headers.get("x-refresh-token")
if (token) {
localStorage.setItem("apollo-token", token)
}
if (refreshToken) {
localStorage.setItem("refresh-token", refreshToken)
}
}
return response
})
)
const errorLink = onError(({ graphQLErrors, networkError }) => {
...
// really long error link code
...
})
let links = [errorLink, afterwareLink, httpLink]
if (process.browser) {
links = [errorLink, afterwareLink, authMiddlewareLink, httpLink]
}
const link = ApolloLink.from(links)
export default function() {
return {
cache,
defaultHttpLink: false,
link
}
}
Is there a way to target ALL mutations/queries with custom headers not just mutations? Or apply some headers to an individual query since I could probably put that as an app middleware?
edit: Haven't solved the SSR portion of this yet.. will re-edit with the answer once I have.

Handling apollo graphql errors globally and render custom ErrorHandler component on error

Need to handle Apollo graphql errors globally in client side and render custom ErrorHandler component on error . So I used Apollo's afterware and apollo-link-error
import ApolloClient from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error'
const httpLink = new HttpLink({ uri: '/graphql' });
const logoutLink = onError(({ networkError }) => {
if (networkError.statusCode === 401) {
//need to dispatch a redux action so that ErrorHandler component renders
}
})
const client = new ApolloClient({
link: logoutLink.concat(httpLink),
});
My solution for this (which I guess, is not the correct approach)
import ApolloClient from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error';
import { render } from 'react-dom';
import ErrorHandler from '../utils/ErrorHandler';
const httpLink = new HttpLink({ uri: '/graphql' });
const logoutLink = onError(({ networkError }) => {
if (networkError.statusCode === 401) {
const targetDiv = document.getElementById('serviceErrorHandler');
render(
<ErrorHandler message={networkError.message}/>,
targetDiv
);
}
})
const client = new ApolloClient({
link: logoutLink.concat(httpLink),
});
Please suggest an approach for my scenario. Thanks in advance
Had this same problem, one solution we went with was making a function that returns onError and take a parameter (the store):
const errorHandler = store => onError((errors) => {
if (errors.networkError) {
store.dispatch(createApolloErrorAction({
message: GENERIC_ERROR_FETCHING_STRING,
}));
}
});
And use it in a wrapper functions to pass in the store:
let apolloClient = null;
export const createApolloClient = (reduxStore) => {
const cache = new InMemoryCache();
const link = errorHandler(reduxStore).concat(otherLink);
apolloClient = new ApolloClient({ link });
return apolloClient;
};
export const getApolloClient = () => apolloClient;

Resources