Add header to all apollo query - vue.js

As the title suggest, I am trying to add a header to all queries and mutations made by apollo. I know I can do
context: {
headers: {
'Accept-Language': $this.i18n.current;
}
}
but that is only for one query or mutation. I am using nuxt with vue and my current nuxt.config.js is as follows
apollo: {
clientConfigs: {
default: '~/plugins/apollo-config.js'
},
defaultOptions: {
$query: {
fetchPolicy: 'network-only',
context: { // does not work
headers: {
"Accept-Language": $this.i18n.current, // not sure if this works as it is in config
}
}
}
},
errorHandler: '~/plugins/apollo-error-handler.js'
},
I'm pretty sure I'm using context wrong in this case but not sure how else I should do it. Any help would be very much appreciated.

I'm not at all a professional regarding GraphQL but last year, I've achieved something that works well (with a JWT header), here is what I had back at the time
nuxt.config.js
apollo: {
clientConfigs: {
default: '#/plugins/nuxt-apollo-config.js',
},
defaultOptions: {
$query: {
loadingKey: 'loading',
fetchPolicy: 'network-only',
},
},
authenticationType: 'Bearer',
},
and here is my
nuxt-apollo-config.js file
import { setContext } from 'apollo-link-context'
import { from } from 'apollo-link'
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'
import { createHttpLink } from '#apollo/client/core'
import schema from '../apollo/schema.json'
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData: schema,
})
export default ({ app, $config: { baseUrlGraphql } }) => {
const headersConfig = setContext(() => ({
credentials: 'same-origin',
headers: {
Authorization: app.$cookies.get('auth._token.local'),
'x-company-id': app.$cookies.get('company_id'),
},
}))
const cache = new InMemoryCache({ fragmentMatcher, resultCaching: false })
return {
defaultHttpLink: false,
link: from([
headersConfig,
createHttpLink({
credentials: 'include',
uri: baseUrlGraphql,
fetch: (uri, options) => {
return fetch(uri, options)
},
}),
]),
cache,
}
}
import { setContext } from 'apollo-link-context' worked well for me. I'm not sure that it's the best because there is maybe something baked-in right now but this one worked for me last year.

Related

Why Vitest mock Axios doesn't work on vuex store testing?

I have the same issue than this post: Why does vitest mock not catch my axios get-requests?
I would like to test my vuex store on vuejs and it works for getters etc but not for actions part with axios get request.
I don't know if it's a good practice to test vuex store than the component in Vue ?
But I guess I need to test both, right ?
a project https://stackblitz.com/edit/vitest-dev-vitest-nyks4u?file=test%2Ftag.spec.js
my js file to test tag.js
import axios from "axios";
const actions = {
async fetchTags({ commit }) {
try {
const response = await axios.get(
CONST_CONFIG.VUE_APP_URLAPI + "tag?OrderBy=id&Skip=0&Take=100"
);
commit("setTags", response.data);
} catch (error) {
console.log(error);
return;
}
},
};
export default {
state,
getters,
actions,
mutations,
};
then my test (tag.spec.js)
import { expect } from "chai";
import { vi } from "vitest";
import axios from "axios";
vi.mock("axios", () => {
return {
default: {
get: vi.fn(),
},
};
});
describe("tag", () => {
test("actions - fetchTags", async () => {
const users = [
{ id: 1, name: "John" },
{ id: 2, name: "Andrew" },
];
axios.get.mockImplementation(() => Promise.resolve({ data: users }));
axios.get.mockResolvedValueOnce(users);
const commit = vi.fn();
await tag.actions.fetchTags({ commit });
expect(axios.get).toHaveBeenCalledTimes(1);
expect(commit).toHaveBeenCalledTimes(1);
});
});
It looks like some other peolpe have the same issues https://github.com/vitest-dev/vitest/issues/1274 but it's still not working.
I try with .ts too but I have exactly the same mistake:
FAIL tests/unit/store/apiObject/tag.spec.js > tag > actions - fetchTags
AssertionError: expected "spy" to be called 1 times
❯ tests/unit/store/apiObject/tag.spec.js:64:24
62| await tag.actions.fetchTags({ commit });
63|
64| expect(axios.get).toHaveBeenCalledTimes(1);
| ^
65| expect(commit).toHaveBeenCalledTimes(1);
66| });
Expected "1"
Received "0"
Thanks a lot for your help.
I finally found the mistake, it was on my vitest.config.ts file, I have to add my global config varaible for my api: import { config } from "#vue/test-utils";
import { defineConfig } from "vitest/config";
import { resolve } from "path";
var configApi = require("./public/config.js");
const { createVuePlugin } = require("vite-plugin-vue2");
const r = (p: string) => resolve(__dirname, p);
export default defineConfig({
test: {
globals: true,
environment: "jsdom",
},
define: {
CONST_CONFIG: configApi,
},
plugins: [createVuePlugin()],
resolve: {
alias: {
"#": r("."),
"~": r("."),
},
// alias: {
// "#": fileURLToPath(new URL("./src", import.meta.url)),
// },
},
});

Apollo query inside nuxt.config.js

I'm trying to make a smart request in nuxt with nuxt-apollo-module in order to grab my routes for the nuxt-sitemaps-module (so I can create my sitemap with them).
I need to make this request from within nuxt.config.js file. I have tried this way with no luck (as app doesn't exist in this context). What would be the right way to do this?
Thanks in advance!
The relevant part of my nuxt.config.js
import gql from 'graphql-tag'
module.exports = {
modules: [
'#nuxtjs/apollo',
'#nuxtjs/sitemap'
],
apollo: {
clientConfigs: {
default: {
httpEndpoint: 'https://example.com/graphql'
}
}
},
sitemap: {
path: '/sitemap.xml',
hostname: 'https://example.com/',
generate: true,
cacheTime: 86400,
trailingSlash: true,
routes: async ({ app }) => {
const myRoutes = ['/one-random-path/']
let client = app.apolloProvider.defaultClient
let myProductsQuery = gql`query {
products {
slug
}
}`
let myBrandsQuery = gql`query {
brands {
slug
}
}`
const myProducts = await client.query({ query: myProductsQuery })
const myBrands = await client.query({ query: myBrandsQuery })
return [myRoutes, ...myProducts, ...myBrands]
}
}
}
I was able to generate it this way, but I'm sure there is a better way.
yarn add node-fetch apollo-boost
sitemap: {
routes: async () => {
const routes = []
const fetch = require("node-fetch")
const { gql } = require("apollo-boost")
const ApolloBoost = require("apollo-boost")
const ApolloClient = ApolloBoost.default
const client = new ApolloClient({
fetch: fetch,
uri: YOUR_API_ENDPOINT,
})
const fetchUsers = gql`
query {
users {
id
}
}
`
const users = await client
.query({
query: fetchUsers,
})
.then((res) => res.data.users)
users.forEach((user) => {
routes.push({
route: `/${user.id}`,
})
})
return routes
},
},
I gave up using Apollo. It was easier to use Axios. Moreover, no nneds to configure #nuxtjs/sitemap :
import axios from 'axios'
sitemap: {
hostname: URL_SITE,
gzip: true,
},
routes() {
return axios({
url: ENDPOINT,
method: 'post',
data: {
query: `
query GET_POSTS {
posts {
nodes {
slug
}
}
}
`,
},
}).then((result) => {
return result.data.data.posts.nodes.map((post) => {
return '/blog/' + post.slug
})
})
}
This was how I was able to do it with authentication. Got round to it by following the documentation here which had a Vue example: https://www.apollographql.com/docs/react/networking/authentication/
Might be a cleaner way to do it with apollo-boost if you can also use auth?
import fetch from 'node-fetch'
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { ApolloLink, concat } from 'apollo-link'
import { InMemoryCache } from 'apollo-cache-inmemory'
import globalQuery from './apollo/queries/global.js'
const httpLink = new HttpLink({ uri: process.env.SCHEMA_URL, fetch })
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const token = process.env.STRAPI_API_TOKEN
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : 'Bearer',
},
})
return forward(operation)
})
export const apolloClient = new ApolloClient({
link: concat(authMiddleware, httpLink),
cache: new InMemoryCache(),
})
export default async () => {
let global = null
const globalResponse = await apolloClient.query({ query: globalQuery })
if (globalResponse?.data?.global?.data) {
global = globalResponse.data.global.data.attributes
}
console.log('globals:::', global)
}

How to set global $axios header in NuxtJS

I've been trying to get this to work for two days now. I'm a brand new user to Nuxt (although I've used Vue for a few years now), so I'm just trying to wrap my brain around how this all works.
In my Nuxt project I have the Axios module installed:
nuxt.config.js
export default {
plugins: [
...
'~/plugins/axios',
],
axios: {
baseURL: 'https://my-url.com/wp-json/wp-v2',
https: true,
},
}
plugins/axios.js
export default ({ $axios, env }) => {
$axios.onRequest(config => {
$axios.setToken(env.WP_API_KEY, 'Bearer');
});
}
And in my page, I'm trying to use the asyncData function to pull data from my WordPress API, as such:
export default {
async asyncData(context) {
const data = await context.$axios.$get('/media');
console.log(data);
return { data };
}
}
I keep receiving a 401 Not Authorized error however, essentially stating that my Authorization: Bearer <token> isn't being passed through. Using Postman however, I can verify that this endpoint does indeed work and returns all of the JSON I need, so the problem must lie in the way I have the axios global header set up.
It's been tough finding any real example on how to set a global header using the Nuxt/Axios module. I see in the docs how to use setToken, however it doesn't exactly show where to place that.
What do I have set up wrong, and how do I fix it?
Pretty typical that I get it working 15 minutes after I post a question.
Setting the header like this instead seems to work. I'm not sure why the setToken method didn't want to work.
export default ({ $axios, env }) => {
$axios.onRequest(config => {
config.headers.common['Authorization'] = `Bearer ${env.WP_API_KEY}`;
});
}
If you are using Nuxt auth module, Here is how I have achived.
// nuxt.config.js
modules: [
'#nuxtjs/auth',
'#nuxtjs/axios',
],
auth: {
strategies: {
local: {
endpoints: {
login: { url: '/auth/login', method: 'post', propertyName: 'accessToken' },
logout: false,
user: { url: '/auth/me', method: 'get', propertyName: false }
},
}
},
redirect: {
login: '/auth/signin',
logout: '/auth/signin',
callback: false,
home: false,
},
cookie: false,
token: {
prefix: 'token',
},
plugins: ['~/plugins/auth.js'],
},
// plugins/axios.js
export default function ({ $axios, $auth, redirect, store }) {
$axios.onRequest((config) => {
config.headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': store.state.auth.tokenlocal, // refers to nuxt.config.js->auth.token
}
})
$axios.onError((error) => {
if (error.response.status === 500) {
redirect('/error')
}
})
}
// store/index.js
export const getters = {
authenticated(state) {
return state.loggedIn;
},
user(state) {
return state.user;
}
};
export const state = () => ({
busy: false,
loggedIn: false,
strategy: "local",
user: false,
});
If you need to customize axios by registering interceptors and changing global config, you have to create a nuxt plugin.
export default ({ $axios, env }) => {
$axios.onRequest(config => {
config.headers.common['Authorization'] = `Bearer ${env.WP_API_KEY}`;
});
}
Adding axios interceptors

Nuxt Apollo Shopify Graphql

So I am using the https://github.com/nuxt-community/apollo-module
I am trying to set this up to connect to my shopify graphql API
On nuxt.config.js:
apollo: {
clientConfigs: {
default: {
httpEndpoint: 'https://my-store.myshopify.com/admin/api/2020-01/graphql.json',
getAuth: () => 'Bearer 26cfd63bbba75243b55fad2c8de0a12f'
},
}
},
on index.vue, i have the following:
<script>
import gql from 'graphql-tag'
export default {
apollo: {
data: {
query: gql`
query {
shop {
name
}
}
`,
}
}
}
</script>
is this the correct set up?
I appear to be getting a cors policy error. I believe this is to do with missing headers that Shopify requires: https://help.shopify.com/en/api/graphql-admin-api/getting-started#authentication
how do I add 'X-Shopify-Access-Token' to the setup?
Any help would be much appreciated.
Thanks
This is how we have it working in our Nuxt Config.
apollo: {
clientConfigs: {
default: {
httpEndpoint:
"http://api.another-backend-example.com/graphql",
persisting: false
},
shopify: {
httpEndpoint:
"https://my-store.myshopify.com/api/2019-07/graphql.json",
httpLinkOptions: {
headers: {
"Content-Type": "application/json",
"X-Shopify-Storefront-Access-Token":
"123456789abcdefghi"
}
},
persisting: false
}
}
}
We also built a lot of useful Shopify components for Nuxt, maybe this helps you: https://github.com/funkhaus/shophaus/

Nuxt Auth Module Authenticated User Data

I have an API api/auth that is used to log users in. It expects to receive an access_token (as URL query, from Headers, or from request body), a username, and a password. I've been using the Vue Chrome Developer Tool and even though I get a 201 response from the server, the auth.loggedIn state is still false. I think that might be the reason why my redirect paths on the nuxt.config.js isn't working as well. Can anyone point me to the right direction on why it doesn't work?
This is a screenshot of the Vue Chrome Developer Tool
This is the JSON response of the server after logging in. The token here is different from the access_token as noted above.
{
"token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"user": {
"user_name": "xxxxxxxxxxxxxxxxxx",
"uid": "xxxxxxxxxxxxxxxxxx",
"user_data": "XXXXXXXXXXXXXXXXXXXXXXXXX"
}
}
Here is the relevant part of nuxt.config.js
export default {
modules: [
'#nuxtjs/axios',
'#nuxtjs/auth',
['bootstrap-vue/nuxt', { css: false }]
],
router: {
middleware: [ 'auth' ]
},
auth: {
strategies: {
local: {
endpoints: {
login: {
url: '/api/auth?access_token=XXXXXXXXXXXXXXXXXXXXXX',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/auth/logout',
method: 'post'
},
user: {
url: '/api/users/me',
method: 'get',
propertyName: 'user'
}
}
}
},
redirect: {
login: '/',
logout: '/',
home: '/home'
},
token: {
name: 'token'
},
cookie: {
name: 'token'
},
rewriteRedirects: true
},
axios: {
baseURL: 'http://localhost:9000/'
}
}
And my store/index.js
export const state = () => ({
authUser: null
})
export const mutations = {
SET_USER: function (state, user) {
state.authUser = user
}
}
export const actions = {
nuxtServerInit ({ commit }, { req }) {
if (req.session && req.user) {
commit('SET_USER', req.user)
}
},
async login ({ commit }, { username, password }) {
const auth = {
username: username,
password: password
}
try {
const { user } = this.$auth.loginWith('local', { auth })
commit('SET_USER', user)
} catch (err) {
console.error(err)
}
}
}
The login action in the store is triggered by this method in the page:
export default {
auth: false,
methods: {
async login () {
try {
await this.$store.dispatch('login', {
username: this.form.email,
password: this.form.password
})
} catch (err) {
this.alert.status = true
this.alert.type = 'danger'
this.alert.response = err
}
}
}
}
P.S. I realize I'm explicitly including the access_token in the URL. Currently, I don't know where a master_key or the like can be set in the Nuxt Auth Module.
Try this in your store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = () => new Vuex.Store({
state: {
authUser: null
},
mutations: {
SET_USER: function (state, user) {
state.authUser = user
}
},
actions: {
CHECK_AUTH: function(token, router) {
if (token === null) {
router.push('/login')
}
}
}
})
export default store
And for the router, this should work globally:
$nuxt._router.push('/')