vue-apollo 3.0.0 Beta configuration - javascript

Pretty new at this so any help much appreciated.
I know how to do Authentication with Apollo client but when I add to my Vue-cli-3 generated project the new vue-apollo-plugin (https://www.npmjs.com/package/vue-apollo). I don't understand how and where to configure my authMiddleware.
Here is the auto generated file form the the cli:
import Vue from 'vue'
import VueApollo from 'vue-apollo'
import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client'
// Install the vue plugin
Vue.use(VueApollo)
// Name of the localStorage item
const AUTH_TOKEN = 'apollo-token'
// Config
const defaultOptions = {
httpEndpoint: process.env.VUE_APP_GRAPHQL_HTTP || 'http://localhost:4000', // Use `null` to disable subscriptions
wsEndpoint: process.env.VUE_APP_GRAPHQL_WS || 'ws://localhost:4000',
// LocalStorage token
tokenName: AUTH_TOKEN,
// Enable Automatic Query persisting with Apollo Engine
persisting: false,
// Use websockets for everything (no HTTP)
// You need to pass a `wsEndpoint` for this to work
websocketsOnly: false,
// Is being rendered on the server?
ssr: false,
// Additional ApolloClient options
// apollo: { ... }
// Client local data (see apollo-link-state)
// clientState: { resolvers: { ... }, defaults: { ... } }
}
// Call this in the Vue app file
export function createProvider (options = {}) {
// Create apollo client
const { apolloClient, wsClient } = createApolloClient({
...defaultOptions,
...options,
})
apolloClient.wsClient = wsClient
// Create vue apollo provider
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
defaultOptions: {
$query: {
// fetchPolicy: 'cache-and-network',
},
},
errorHandler (error) {
// eslint-disable-next-line no-console
console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message)
},
})
return apolloProvider
}
// Manually call this when user log in
export async function onLogin (apolloClient, token) {
localStorage.setItem(AUTH_TOKEN, token)
if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient)
try {
await apolloClient.resetStore()
} catch (e) {
// eslint-disable-next-line no-console
console.log('%cError on cache reset (login)', 'color: orange;', e.message)
}
}
// Manually call this when user log out
export async function onLogout (apolloClient) {
localStorage.removeItem(AUTH_TOKEN)
if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient)
try {
await apolloClient.resetStore()
} catch (e) {
// eslint-disable-next-line no-console
console.log('%cError on cache reset (logout)', 'color: orange;', e.message)
}
}
I have what I would previously use for authentication via the header here:
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const token = localStorage.getItem(AUTH_TOKEN)
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : null
}
})
return forward(operation)
})
It seems like when I dig a bit deeper into some of the imported objects from the vue-apollo package there is something like this already built in in the createApolloClient object it has this property:
authLink = setContext(function (_, _ref2) {
var headers = _ref2.headers;
return {
headers: _objectSpread({}, headers, {
authorization: getAuth(tokenName)
})
};
});
Does this mean I can simply destructure the property off the createApolloClient object? Any help or tips much appreciated.

Take a look at vue-cli-plugin-apollo
You can pass a link: authLink and\or getAuth:()=>{return "something"} in const defaultOptions = { ... } in /vue-apollo.js.
Or in main.js when you call createProvider
new Vue({
// router, store
apolloProvider: createProvider({
link: authLink,
getAuth: AUTH_TOKEN => localStorage.getItem(AUTH_TOKEN)
}),
// ...
})
using both if you adding header in authLink, getAuth is probably redundant.
if you plan to use more than one link, there is apollo-link package link: ApolloLink.from([ ... ])

Related

I am losing user information (session) on refreshing and between tabs while using iron-session and next.js

I am currently working on a project using iron-session and next.js. I don't lose my user on clicking Link tags. But if I refresh user becomes undefined. Cookie is set and doesn't get deleted on refresh. I don't know what is wrong.
Here is my login.ts code:
export default withIronSessionApiRoute(loginRoute, sessionOptions);
async function loginRoute(req: NextApiRequest, res: NextApiResponse) {
try {
const {
data: {tcId, admin, userName},
} = await axios('http://localhost:8080/user/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
data: JSON.stringify(req.body),
});
const user = {tcId: tcId, userName: userName, admin: admin} as User;
req.session.user = user;
await req.session.save();
res.json(user);
} catch (error) {
res.status(500).json({message: (error as Error).message})
}}
Here is my session.ts code:
// this file is a wrapper with defaults to be used in both API routes and `getServerSideProps` functions
import type { IronSessionOptions } from 'iron-session'
import type { User } from '../pages/api/user'
export const sessionOptions: IronSessionOptions = {
password: process.env.SECRET_COOKIE_PASSWORD as string,
cookieName: 'eys-cookie',
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
cookieOptions: {
secure: process.env.NODE_ENV === 'production',
},
};
// This is where we specify the typings of req.session.*
declare module 'iron-session' {
interface IronSessionData {
user?: User
}
}
As I said previously. I don't lose my user. While routing around using Link tags from next.
Refreshing causes to lose my user. Also other tabs don't reach my user.
I can show more code if wanted. But I think problem is here.
I had a similar issue and solved the tab switching by setting:
revalidateOnFocus = false,
for the useSWR function
sample
const fetcher = (url: any) => fetch(url).then((res) => res.json());
const options = {
revalidateOnFocus: false,
};
const {
data: session,
error,
mutate: mutateUser,
} = useSWR<Session>("/api/session", fetcher, options);
you can also explore other options here https://swr.vercel.app/docs/options
Probably you forgot to add SWRconfig on the index or app component:
import { AppProps } from 'next/app'
import { SWRConfig } from 'swr'
import fetchJson from 'lib/fetchJson'
function MyApp({ Component, pageProps }: AppProps) {
return (
<SWRConfig
value={{
fetcher: fetchJson,
onError: (err) => {
console.error(err)
},
}}
>
<Component {...pageProps} />
</SWRConfig>
)
}
export default MyApp

Using react hooks inside NextJS /pages/api [duplicate]

I need a graphql client lib to run on node.js for some testing and some data mashup - not in a production capacity. I'm using apollo everywhere else (react-apollo, apollo's graphql-server-express). My needs are pretty simple.
Is apollo-client a viable choice? I can find no examples or docs on using it on node - if you're aware of any, please share.
Or maybe I should/can use the reference graphql client on node?
Apollo Client should work just fine on Node. You only have to install cross-fetch.
Here is a complete TypeScript implementation of Apollo Client working on Node.js.
import { ApolloClient, gql, HttpLink, InMemoryCache } from "#apollo/client";
import { InsertJob } from "./graphql-types";
import fetch from "cross-fetch";
const client = new ApolloClient({
link: new HttpLink({ uri: process.env.PRODUCTION_GRAPHQL_URL, fetch }),
cache: new InMemoryCache(),
});
client.mutate<InsertJob.AddCompany, InsertJob.Variables>({
mutation: gql`mutation insertJob($companyName: String!) {
addCompany(input: { displayName: $companyName } ) {
id
}
}`,
variables: {
companyName: "aaa"
}
})
.then(result => console.log(result));
Newer Apollo version provide a simpler approach to perform this, as described in Apollo docs, check the section "Standalone". Basically one can simply use ApolloLink in order to perform a query or mutation.
Below is copy of the example code from the docs as of writing this, with node-fetch usage as config to createHttpLink. Check the docs for more details on how to use these tools.
import { execute, makePromise } from 'apollo-link';
import { createHttpLink } from 'apollo-link-http';
import gql from 'graphql-tag';
import fetch from 'node-fetch';
const uri = 'http://localhost:4000/graphql';
const link = createHttpLink({ uri, fetch });
const operation = {
query: gql`query { hello }`,
variables: {} //optional
operationName: {} //optional
context: {} //optional
extensions: {} //optional
};
// execute returns an Observable so it can be subscribed to
execute(link, operation).subscribe({
next: data => console.log(`received data: ${JSON.stringify(data, null, 2)}`),
error: error => console.log(`received error ${error}`),
complete: () => console.log(`complete`),
})
// For single execution operations, a Promise can be used
makePromise(execute(link, operation))
.then(data => console.log(`received data ${JSON.stringify(data, null, 2)}`))
.catch(error => console.log(`received error ${error}`))
If someone is looking for a JavaScript version:
require('dotenv').config();
const gql = require('graphql-tag');
const ApolloClient = require('apollo-boost').ApolloClient;
const fetch = require('cross-fetch/polyfill').fetch;
const createHttpLink = require('apollo-link-http').createHttpLink;
const InMemoryCache = require('apollo-cache-inmemory').InMemoryCache;
const client = new ApolloClient({
link: createHttpLink({
uri: process.env.API,
fetch: fetch
}),
cache: new InMemoryCache()
});
client.mutate({
mutation: gql`
mutation popJob {
popJob {
id
type
param
status
progress
creation_date
expiration_date
}
}
`,
}).then(job => {
console.log(job);
})
You can make apollo-client work, but it's not the best option for this use case.
Try graphql-request instead.
Minimal GraphQL client supporting Node and browsers for scripts or simple apps
Features per npmjs:
Most simple & lightweight GraphQL client
Promise-based API (works with async / await)
Typescript support
Isomorphic (works with Node / browsers)
example:
import { request, gql } from 'graphql-request'
const query = gql`
{
Movie(title: "Inception") {
releaseDate
actors {
name
}
}
}
`
request('https://api.graph.cool/simple/v1/movies', query).then((data) => console.log(data))
I have no affiliation with this package.
Here is simple node js implementation.
'graphiql' client is good enough for development activities.
1. run npm install
2. start server with "node server.js"
3. hit "http://localhost:8080/graphiql" for graphiql client
server.js
var graphql = require ('graphql').graphql
var express = require('express')
var graphQLHTTP = require('express-graphql')
var Schema = require('./schema')
// This is just an internal test
var query = 'query{starwar{name, gender,gender}}'
graphql(Schema, query).then( function(result) {
console.log(JSON.stringify(result,null," "));
});
var app = express()
.use('/', graphQLHTTP({ schema: Schema, pretty: true, graphiql: true }))
.listen(8080, function (err) {
console.log('GraphQL Server is now running on localhost:8080');
});
schema.js
//schema.js
var graphql = require ('graphql');
var http = require('http');
var StarWar = [
{
"name": "default",
"gender": "default",
"mass": "default"
}
];
var TodoType = new graphql.GraphQLObjectType({
name: 'starwar',
fields: function () {
return {
name: {
type: graphql.GraphQLString
},
gender: {
type: graphql.GraphQLString
},
mass: {
type: graphql.GraphQLString
}
}
}
});
var QueryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: function () {
return {
starwar: {
type: new graphql.GraphQLList(TodoType),
resolve: function () {
return new Promise(function (resolve, reject) {
var request = http.get({
hostname: 'swapi.co',
path: '/api/people/1/',
method: 'GET'
}, function(res){
res.setEncoding('utf8');
res.on('data', function(response){
StarWar = [JSON.parse(response)];
resolve(StarWar)
console.log('On response success:' , StarWar);
});
});
request.on('error', function(response){
console.log('On error' , response.message);
});
request.end();
});
}
}
}
}
});
module.exports = new graphql.GraphQLSchema({
query: QueryType
});
In response to #YakirNa 's comment:
I can't speak to the other needs I described, but I have done a fair amount of testing. I ended up doing all of my testing in-process.
Most testing ends up being resolver testing, which I do via a jig that invokes the graphql library's graphql function with a test query and then validates the response.
I also have an (almost) end-to-end test layer that works at the http-handling level of express. It creates a fake HTTP request and verifies the response in-process. This is all within the server process; nothing goes over the wire. I use this lightly, mostly for testing JWT authentication and other request-level behavior that's independent of the graphql request body.
I was running into your same question, because I wanted to create a middleware service to prepare data from graphQL to a final frontend application,
to have :
optimised data representation (and standard output data interface)
faster response time
assuming that graphQL server is provided by an external provider , so no ownership to data model, directly with GQL
So I didn't want to implement GraphQL Apolloclient directly in a frontend framework like React / Angular, Vuejs... but manage the queries via Nodejs at backend of a REST API.
So this is the class wrapper for Apolloclient I was able to assemble (using typescript):
import ApolloClient from "apollo-client";
import { ApolloLink } from 'apollo-link'
import { HttpLink } from 'apollo-link-http'
import { onError } from 'apollo-link-error'
import fetch from 'node-fetch'
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'
import introspectionQueryResultData from '../../fragmentTypes.json';
import { AppConfig } from 'app-config';
const config: AppConfig = require('../../../appConfig.js');
export class GraphQLQueryClient {
protected apolloClient: any;
constructor(headers: { [name: string]: string }) {
const api: any = {
spaceId: config.app.spaceId,
environmentId: config.app.environmentId,
uri: config.app.uri,
cdnApiPreviewToken: config.cdnApiPreviewToken,
};
// console.log(JSON.stringify(api));
const ACCESS_TOKEN = api.cdnApiPreviewToken;
const uri = api.uri;
console.log(`Apollo client setup to query uri: ${uri}`);
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData
});
this.apolloClient = new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }:any) => {
if (graphQLErrors) {
graphQLErrors.map((el:any) =>
console.warn(
el.message || el
)
)
graphQLErrors.map(({ message, locations, path }:any) =>
console.warn(
`[GraphQL error - Env ${api.environmentId}]: Message: ${message}, Location: ${JSON.stringify(locations)}, Path: ${path}`
)
)
}
if (networkError) console.log(`[Network error]: ${networkError}`)
}),
new HttpLink({
uri,
credentials: 'same-origin',
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`
},
fetch
})
]),
cache: new InMemoryCache({ fragmentMatcher }),
// fetchPolicy as network-only avoids using the cache.
defaultOptions: {
watchQuery: {
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
},
query: {
fetchPolicy: 'network-only',
errorPolicy: 'all',
},
}
});
}
}
After this constructor I run queries like :
let response = await this.apolloClient.query({ query: gql`${query}` });
As you might have noticed:
I needed to inject fetch on Httplink
I had to setup Authorization headers to access external provider graphQL endpoint
I used IntrospectionFragmentMatcher in order to use Fragments in my queries, along with building schema type ("fragmentTypes.json" with an init script)
Posting this to just add my experience and maybe more info for the question.
Also looking forward for comments and points of improvement for this wrapper.

Nuxt auth update auth on custom requests

From nuxt auth website I saw this:
setUserToken(token)
Returns: Promise
Set the auth token and fetch the user using the new token and current strategy.
TIP: This function can properly set the user after registration
this.$auth.setUserToken(token)
.then(() => this.$toast.success('User set!'))
Tried to use it and it said method is undefined, looked up in the source files and none of methods are like this one.
I am not very good with this but, how would I set user and token with nuxt/auth module after registration or anything but login/loginWith?
If there is no option for that why is it there on documentation?
I would also need to know if I need to create custom auth do I need to use both cookies and localstorage or just one of them?
It says that cookies are used for server side and storage for client side.
Can I use just cookies and on nuxtServerInit get cookie for token and set token and user data fetched by api within vuex store? Then use it from there if it is needed?
Nuxt/auth module hurt my brain so long and today I created custom module:
First I have this store structure:
store/
-- index.js
-- mutations.js
-- actions.js
-- state.js
-- getters.js
middleware/
-- redirectIfAuth.js
-- redirectIfNotAuth.js
layouts/
default.vue -> has redirectIfNotAuth.js
guest.vue -> has redirectIfAuth.js
pages/
-- login/
---- index.vue -> uses guest.vue as layout
-- dashboard/
----- index.vue -> uses default.vue as layout without declaration
Inside Index.js I have:
import state from './state'
import * as actions from './actions'
import * as mutations from './mutations'
import * as getters from './getters'
export default {
state,
getters,
mutations,
actions,
modules: {}
}
Inside State.js I have:
export default () => ({
user: null,
token: null,
headers: null
})
Inside Actions.js I have:
const cookieparser = process.server ? require('cookieparser') : undefined
// importing server based cookie library
export async function nuxtServerInit ({ commit }, { req, res }) {
// If we have any axios requests we need to add async/await
// And since this works on server mode, we don't need to check is it server
let token = null
if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie)
try {
token = parsed.authToken
} catch (e) {
console.log(e)
}
}
// If we have token within cookies we get user data from api and we pass Autorization headers with token
if (token !== null && token !== false) {
await axios.get('/api/auth/me', {
headers: {
'Authorization': token
}
}).then((response) => {
// If we get user data we set it to store
commit('setUser', response.data.data)
commit('setToken', token)
commit('setHeaders', token)
}).catch((error) => {
// If we get error, we should logout user by removing data within cookies and store
// Additionally you can create specific code error on backend to check if token is expired or invalid
// and then check for status code and then remove data
commit('setUser', null)
commit('setToken', null)
res.setHeader('Set-Cookie', [`authToken=false; expires=Thu, 01 Jan 1970 00:00:00 GMT`])
// This is only way I found useful for removing cookies from node server
console.warn(error)
})
}
}
Inside Mutations.js I have:
export const setToken = (state, payload) => state.token = payload
export const setUser = (state, payload) => state.user = payload
export const setHeaders = (state, payload) => {
state.headers = {
headers: {
'Authorization': payload
}
}
}
Inside Getters.js I have:
export const getUser = (state) => state.user
export const getToken = (state) => state.token
export const getHeaders = (state) => state.headers
Second I created two middlewares and it seems like nuxt middlewares work on both server and client sides, so I needed to require both libraries for server and client side Then I checked which side it is and then try to get token for further investigations If you include and don't check for server and client but use one of them, your templates wont render but show undefined errors for req on client instead and on server it wont show anything.
Inside redirectIfAuth.js I have:
const cookieparser = process.server ? require('cookieparser') : undefined
const Cookie = process.client ? require('js-cookie') : undefined
export default function ({ app, redirect, req }) {
let token = null
if (process.server) {
if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie)
try {
token = parsed.authToken
} catch (e) {
console.log(e)
}
}
} else if (process.client) {
token = Cookie.get('authToken')
}
if (token && token !== false) {
app.store.commit('setToken', token)
app.store.commit('setHeaders', token)
if (app.store.state.user) {
if (app.store.state.user.roles.includes('customer')) {
return redirect({
name: 'customer-slug',
params: { slug: app.store.state.user.username }
})
} else if (app.store.state.user.roles.includes('admin')) {
return redirect({
name: 'dashboard'
})
} else {
return redirect({
name: 'index'
})
}
} else {
return redirect({
name: 'index'
})
}
}
}
Inside redirectIfNotAuth.js I have:
const cookieparser = process.server ? require('cookieparser') : undefined
const Cookie = process.client ? require('js-cookie') : undefined
export default function ({ app, redirect, route, req }) {
let token = null
if (process.server) {
if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie)
try {
token = parsed.authToken
} catch (e) {
console.log(e)
}
}
} else if (process.client) {
token = Cookie.get('authToken')
}
if (token === null || token === false) {
return redirect({
name: 'login',
query: {
redirect: route.fullPath
}
})
}
}
Now we use these middlewares within pages or layouts as:
export default {
middleware: ['redirectIfAuth']
}
Or
export default {
middleware: ['redirectIfNotAuth']
}
Login:
async login () {
if (this.form.email !== '' && this.form.password !== '') {
await this.$axios.post('/api/auth/login', this.form).then((response) => {
this.$store.commit('setUser', response.data.data)
this.$store.commit('setToken', 'Bearer ' + response.data.meta.access_token)
this.$store.commit('setHeaders', 'Bearer ' + response.data.meta.access_token)
Cookie.set('authToken', 'Bearer ' + response.data.meta.access_token, { expires: 365 })
// Cookie.set('authUser', response.data.data, { expires: 365 }) if you need user data within cookies
if (this.$route.query.redirect) {
this.$router.push(this.$route.query.redirect)
}
this.$router.push('/')
})
}
}
Logout:
async logout () {
await this.$axios.post('/api/auth/logout', {}, this.headers)
// Cookie.remove('authUser') if exists
Cookie.remove('authToken')
this.$router.push('/')
}
I hope this helps someone or someone get idea from this to make something else. I had million problems with official nuxt auth and only this helped me sort things out...

Nuxt-i18n: how to load messages asynchronously?

I am building a multilingual Nuxt web app.
Using this example from the official documentation (Codepen link), I no longer want to use local JSON files where my translations are saved to work as defined in the code below:
messages: {
'en': require('~/locales/en.json'), # I want to get this asynchronously from an HTTP URL
'fr': require('~/locales/fr.json') # I want to get this asynchronously from an HTTP URL
}
I wonder what available alternatives to set asynchronously en and fr values by reading the JSON data from a URL instead?
plugins/i18n.js:
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
export default ({ app, store }) => {
// Set i18n instance on app
// This way we can use it in middleware and pages asyncData/fetch
app.i18n = new VueI18n({
locale: store.state.locale,
fallbackLocale: 'en',
messages: {
'en': require('~/locales/en.json'), # How to get this asynchronously?
'fr': require('~/locales/fr.json') # # How to get this asynchronously?
}
})
app.i18n.path = (link) => {
if (app.i18n.locale === app.i18n.fallbackLocale) {
return `/${link}`
}
return `/${app.i18n.locale}/${link}`
}
}
What I tried:
messages: {
'en': axios.get(url).then((res) => {
return res.data
} ),
'fr': require('~/locales/fr.json')
}
Where url points to the /locals/en.json file which is hosted on my Github profile.
You can use axios with await directly in the constructor:
export default async ({ app, store }) => {
app.i18n = new VueI18n({ //construction a new VueI18n
locale: store.state.i18n.locale,
fallbackLocale: 'de',
messages: {
'de': await axios.get('http://localhost:3000/lang/de.json').then((res) => {
return res.data
}),
'en': await axios.get('http://localhost:3000/lang/en.json').then((res) => {
return res.data
})
}
})
}
I've a solution with localise.biz and cross-fetch
First add async to the pluginsplugins / i18n.js function and add await to the remote translation calls :
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import getMessages from './localize';
Vue.use(VueI18n);
export default async ({ app, store }) => {
app.i18n = new VueI18n({
locale: store.state.locale,
fallbackLocale: 'en',
messages: {
'en': await getMessages('en'),
'fr': await getMessages('fr')
}
});
app.i18n.path = (link) => {
if (app.i18n.locale === app.i18n.fallbackLocale) return `/${link}`;
return `/${app.i18n.locale}/${link}`;
}
}
And create new function for get remote translation :
import fetch from 'cross-fetch';
const LOCALIZE_API_KEY = 'XXXXXXXXXXX';
const LOCALIZE_URL = 'https://localise.biz/api/export/locale';
const HEADERS = {
'Authorization': `Loco ${LOCALIZE_API_KEY}`
};
const getMessages = async (locale) => {
const res = await fetch(`${LOCALIZE_URL}/${locale}.json`, { headers: HEADERS });
if (res.status >= 400) throw new Error("Bad response from server");
return await res.json();
};
export default getMessages;
This is what i ended up with:
async asyncData(context){
// fetch translation for your source
var translation = await fetch('/translation')
// get locale of current page
var locale = context.app.i18n.locale
// set translation for Server Side Rendering
context.app.i18n.mergeLocaleMessage(locale, translation)
// save it for use on client side
return {translation: translation}
},
created(){
// prevent reverting back to not found after hard-loading page.
this.$i18n.mergeLocaleMessage(this.$i18n.locale, this.translation)
}

Wrapping a RESTapi with GraphQL using Apollo React

I need to do a project (currency exchange app) using Apollo client and React. I need to wrap an existing REST api (fixer.io) with graphql. So far no luck finding a solution online. Tried several tutorials but they don't seem to work. Anyone have experience with this?
Thanks.
I assume you use Apollo client 2.0 and want everything to be client side.
First you need an apollo bridge link. It's used "When you don't have GraphQL server (yet) and want to use GraphQL on the client". Its source code is quite short, so you can inline it:
/*
Copyright (c) 2017 David Cizek
https://github.com/dacz/apollo-bridge-link
*/
import { GraphQLSchema, graphql, print } from 'graphql';
import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools';
import { ApolloLink } from 'apollo-link';
import Observable from 'zen-observable';
export const createBridgeLink = ({ schema, resolvers, mock, context = {} }) => {
let executableSchema;
if (typeof schema === 'string') {
executableSchema = makeExecutableSchema({ typeDefs: schema, resolvers });
} else if (schema.kind === 'Document') {
executableSchema = makeExecutableSchema({
typeDefs: print(schema),
resolvers,
});
} else if (schema instanceof GraphQLSchema) {
executableSchema = schema;
} else {
throw new Error('schema should be plain text, parsed schema or executable schema.');
}
if (mock)
{addMockFunctionsToSchema({
schema: executableSchema,
preserveResolvers: true,
});}
return new ApolloLink(
operation =>
new Observable(observer => {
const { headers, credentials } = operation.getContext();
const ctx = {
...context,
headers,
credentials,
};
graphql(executableSchema, print(operation.query), undefined, ctx, operation.variables, operation.operationName)
.then(data => {
observer.next(data);
observer.complete();
})
.catch(err => {
/* istanbul ignore next */
observer.error(err);
});
}),
);
};
export class BridgeLink extends ApolloLink {
requester;
constructor(opts) {
super();
this.requester = createBridgeLink(opts).request;
}
request(op) {
return this.requester(op);
}
}
Next you create schema and resolvers:
// schema.js
export default `
type Rate {
date: Date!
rate: Float!
}
type Query {
latestRate(from: String!, to: String!): Rate
}
schema {
query: Query
}
`;
// resolvers.js
const resolvers = {
Query: {
latestRate(obj, args, context, info) {
return fetch(`https://api.fixer.io/latest?base=${args.from}`).then(res => res.json())
.then(res => { date: res.date, rate: res.rates[args.to] })
}
}
}
export default resolvers;
Finally, you create an apollo client factory:
// clientFactory.js
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { BridgeLink } from './apollo-bridge-link';
import schema from './schema';
import resolvers from './resolvers';
export default () => {
const mock = false;
const context = {};
const client = new ApolloClient({
link: new BridgeLink({ schema, resolvers, mock, context }),
cache: new InMemoryCache(),
});
return client;
};
Here's how you use it:
import gql from 'graphql-tag';
import clientFactory from './clientFactory'
const client = clientFactory();
client.query(gql`query {
latestRate(from: "USD", to: "EUR") { date, rate }
}`).then(console.log)
If you want to use it in React:
import { ApolloProvider } from 'react-apollo';
const client = clientFactory();
const App = ({ data: { latestRate, refetch } }) => {
return <div>
<span>Today:</span><span>{latestRate.date}</span>
<span>1 USD equals:</span><span>{latestRate.rate} EUR</span>
<button onClick={() => refetch()}>
Refresh
</button>
</div>
}
const AppWithQuery = graphql(gql`
query {
latestRate(from: "USD", to: "EUR") { date, rate }
}
`)(App);
ReactDOM.render(
<ApolloProvider client={client}>
<AppWithQuery/>
</ApolloProvider>,
document.getElementById('root'),
);
With the Graphcool Framework, you can define resolver functions, which allow you to easily wrap any REST API. You can define a function and connect it to a specific mutation or query in your GraphQL Schema.
I prepared a demo, wrapping the fixer API.
Try to run this query to get the exchange rates with USD as base, for example:
query {
fixer(
base: "USD"
) {
base
date
eur
usd
rub
}
}
You can build this demo yourself like this:
git clone git#github.com:graphcool/templates.git
cd templates/curated/misc/fixer-wrapper
npm install -g graphcool#next
graphcool init
graphcool deploy
graphcool playground
Please feel free to share any improvement you might have in mind, the example is open source. You can read more about resolvers here.

Categories

Resources