Setting Global Axios Headers in Vue 3 - django

I am trying to use Axios to hit my backend (Django), but I am having some trouble setting my global headers to include the CSRF token in the header.
This is reaching my server:
import axios from "axios";
async function loadCards() {
var instance = axios.create({
xsrfCookieName: window.rootData.csrfToken,
xsrfHeaderName: "X-CSRFTOKEN",
});
return await instance.post(window.rootData.urlpaths.loadCards, {
'state': props.state.label,
'filter': props.filter.label,
'project': window.rootData.project
})
}
However, I want these headers to apply to all of my internal api requests. So I thought I would establish them in a separate file:
axios-site.js
import axios from "axios";
var siteApi = axios.create({
xsrfCookieName: window.rootData.csrfToken,
xsrfHeaderName: "X-CSRFTOKEN",
});
export default {
siteApi
}
Vue Component
import siteApi from "#/axios-site";
setup () {
async function loadCards() {
return await siteApi.post(window.rootData.urlpaths.loadCards, {
'state': props.state.label,
'filter': props.filter.label,
'project': window.rootData.project
})
}
}
Here is the error in console:
Uncaught (in promise) TypeError: _axios_site__WEBPACK_IMPORTED_MODULE_4__.default.post is not a function
at _callee$ (ActionColumn.vue?ba4f:97)
at tryCatch (runtime.js?96cf:63)
at Generator.invoke [as _invoke] (runtime.js?96cf:293)
at Generator.eval [as next] (runtime.js?96cf:118)
at asyncGeneratorStep (asyncToGenerator.js?1da1:3)
at _next (asyncToGenerator.js?1da1:25)
at eval (asyncToGenerator.js?1da1:32)
at new Promise (<anonymous>)
at eval (asyncToGenerator.js?1da1:21)
at _loadCards (ActionColumn.vue?ba4f:80)
It seems something is being lost when I run it through the external file. I'm sure I am missing something obvious, but I can't seem to pinpoint it. I found another accepted answer that follows a similar logic here, but it isn't working in my case. Is it possible that webpack is screwing things up?

You should export the axios instance like :
export default siteApi
then in main.js add it to globalProperties :
import siteApi from "#/axios-site";
...
app.config.globalProperties.siteApi=siteApi
in any child component you'll have access to that property :
import { getCurrentInstance } from 'vue'
const MyComponent = {
setup() {
const internalInstance = getCurrentInstance()
const {siteApi}= internalInstance.appContext.config.globalProperties
async function loadCards() {
return await siteApi.post(window.rootData.urlpaths.loadCards, {
'state': props.state.label,
'filter': props.filter.label,
'project': window.rootData.project
})
}
}
}
in options api like mounted hook :
mounted(){
this.siteApi.post(...)
}

Related

How to block external requests to a NextJS Api route

I am using NextJS API routes to basically just proxy a custom API built with Python and Django that has not yet been made completely public, I used the tutorial on Vercel to add cors as a middleware to the route however it hasn't provided the exact functionality I wanted.
I do not want to allow any person to make a request to the route, this sort of defeats the purpose for but it still at least hides my API key.
Question
Is there a better way of properly stopping requests made to the route from external sources?
Any answer is appreciated!
// Api Route
import axios from "axios";
import Cors from 'cors'
// Initializing the cors middleware
const cors = Cors({
methods: ['GET', 'HEAD'],
allowedHeaders: ['Content-Type', 'Authorization','Origin'],
origin: ["https://squadkitresearch.net", 'http://localhost:3000'],
optionsSuccessStatus: 200,
})
function runMiddleware(req, res, fn) {
return new Promise((resolve, reject) => {
fn(req, res, (res) => {
if (res instanceof Error) {
return reject(res)
}
return resolve(res)
})
})
}
async function getApi(req, res) {
try {
await runMiddleware(req, res, cors)
const {
query: { url },
} = req;
const URL = `https://xxx/api/${url}`;
const response = await axios.get(URL, {
headers: {
Authorization: `Api-Key xxxx`,
Accept: "application/json",
}
});
if (response.status === 200) {
res.status(200).send(response.data)
}
console.log('Server Side response.data -->', response.data)
} catch (error) {
console.log('Error -->', error)
res.status(500).send({ error: 'Server Error' });
}
}
export default getApi
Sorry for this late answer,
I just think that this is the default behaviour of NextJS. You are already set, don't worry. There is in contrast, a little bit customization to make if you want to allow external sources fetching your Next API

How to initialize ApolloClient in SvelteKit to work on both SSR and client side

I tried but didn't work. Got an error: Error when evaluating SSR module /node_modules/cross-fetch/dist/browser-ponyfill.js:
<script lang="ts">
import fetch from 'cross-fetch';
import { ApolloClient, InMemoryCache, HttpLink } from "#apollo/client";
const client = new ApolloClient({
ssrMode: true,
link: new HttpLink({ uri: '/graphql', fetch }),
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache()
});
</script>
With SvelteKit the subject of CSR vs. SSR and where data fetching should happen is a bit deeper than with other somewhat "similar" solutions. The bellow guide should help you connect some of the dots, but a couple of things need to be stated first.
To define a server side route create a file with the .js extension anywhere in the src/routes directory tree. This .js file can have all the import statements required without the JS bundles that they reference being sent to the web browser.
The #apollo/client is quite huge as it contains the react dependency. Instead, you might wanna consider importing just the #apollo/client/core even if you're setting up the Apollo Client to be used only on the server side, as the demo bellow shows. The #apollo/client is not an ESM package. Notice how it's imported bellow in order for the project to build with the node adapter successfully.
Try going though the following steps.
Create a new SvelteKit app and choose the 'SvelteKit demo app' in the first step of the SvelteKit setup wizard. Answer the "Use TypeScript?" question with N as well as all of the questions afterwards.
npm init svelte#next demo-app
cd demo-app
Modify the package.json accordingly. Optionally check for all packages updates with npx npm-check-updates -u
{
"name": "demo-app",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build --verbose",
"preview": "svelte-kit preview"
},
"devDependencies": {
"#apollo/client": "^3.3.15",
"#sveltejs/adapter-node": "next",
"#sveltejs/kit": "next",
"graphql": "^15.5.0",
"node-fetch": "^2.6.1",
"svelte": "^3.37.0"
},
"type": "module",
"dependencies": {
"#fontsource/fira-mono": "^4.2.2",
"#lukeed/uuid": "^2.0.0",
"cookie": "^0.4.1"
}
}
Modify the svelte.config.js accordingly.
import node from '#sveltejs/adapter-node';
export default {
kit: {
// By default, `npm run build` will create a standard Node app.
// You can create optimized builds for different platforms by
// specifying a different adapter
adapter: node(),
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte'
}
};
Create the src/lib/Client.js file with the following contents. This is the Apollo Client setup file.
import fetch from 'node-fetch';
import { ApolloClient, HttpLink } from '#apollo/client/core/core.cjs.js';
import { InMemoryCache } from '#apollo/client/cache/cache.cjs.js';
class Client {
constructor() {
if (Client._instance) {
return Client._instance
}
Client._instance = this;
this.client = this.setupClient();
}
setupClient() {
const link = new HttpLink({
uri: 'http://localhost:4000/graphql',
fetch
});
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
return client;
}
}
export const client = (new Client()).client;
Create the src/routes/qry/test.js with the following contents. This is the server side route. In case the graphql schema doesn't have the double function specify different query, input(s) and output.
import { client } from '$lib/Client.js';
import { gql } from '#apollo/client/core/core.cjs.js';
export const post = async request => {
const { num } = request.body;
try {
const query = gql`
query Doubled($x: Int) {
double(number: $x)
}
`;
const result = await client.query({
query,
variables: { x: num }
});
return {
status: 200,
body: {
nodes: result.data.double
}
}
} catch (err) {
return {
status: 500,
error: 'Error retrieving data'
}
}
}
Add the following to the load function of routes/todos/index.svelte file within <script context="module">...</script> tag.
try {
const res = await fetch('/qry/test', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
num: 19
})
});
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
Finally execute npm install and npm run dev commands. Load the site in your web browser and see the server side route being queried from the client whenever you hover over the TODOS link on the navbar. In the console's network tab notice how much quicker is the response from the test route on every second and subsequent request thanks to the Apollo client instance being a singleton.
Two things to have in mind when using phaleth solution above: caching and authenticated requests.
Since the client is used in the endpoint /qry/test.js, the singleton pattern with the caching behavior makes your server stateful. So if A then B make the same query B could end up seeing some of A data.
Same problem if you need authorization headers in your query. You would need to set this up in the setupClient method like so
setupClient(sometoken) {
...
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: `Bearer ${sometoken}`
}
};
});
const client = new ApolloClient({
credentials: 'include',
link: authLink.concat(link),
cache: new InMemoryCache()
});
}
But then with the singleton pattern this becomes problematic if you have multiple users.
To keep your server stateless, a work around is to avoid the singleton pattern and create a new Client(sometoken) in the endpoint.
This is not an optimal solution: it recreates the client on each request and basically just erases the cache. But this solves the caching and authorization concerns when you have multiple users.

VueComponent.mounted : TypeError: Cannot read property 'get' of undefined in mounted hook

I am using jest for unit testing in nuxt js
I have mounted hook like this
async mounted(){
try{
var response = await this.$axios.get("api_url here");
this.result = response.data;
} catch(e){
console.log("Exception: ",e)
}
}
when i do unit test for it my code is . utnit.spec.js
jest.mock("axios", () => ({
get: () => Promise.resolve({ data: [{ val: 1 }] })
}));
import { mount } from '#vue/test-utils';
import file from '../filefile';
import axios from "axios";
describe('file', () => {
test('check comp. working correctly', () => {
var wrapper = mount(file);
afterEach(() => {
wrapper.destroy()
})
})
})
I am getting this warn there and there is no data in the results
Exception: TypeError: Cannot read property 'get' of undefined
at VueComponent.mounted
how do I know what is the problem here, is this I can not access axios in the unit file Is there any specific way to test Axios in mounted hook
The error means that it's this.$axios.get that is not available, not axios.get. The component relies on Axios plugin that is commonly installed in Vue application entry point or Nuxt configuration.
It can be installed for localVue Vue instance in tests, or be supplied directly to the component:
var wrapper = mount(file, { mocks: { $axios: axios } });
Also, the mock will fail if Axios is used as axios() somewhere because default import is expected to be a function:
jest.mock("axios", () => Object.assign(
jest.fn(),
{ get: jest.fn() }
));
axios.get is Jest spy, the implementation is mocked per test depending on the use and isn't limited to hard-coded Promise.resolve({ data: ... }) supplied in the mock.

Apollo-client returns "400 (Bad Request) Error" on sending mutation to server

I am currently using the vue-apollo package for Apollo client with VueJs stack with django and graphene-python for my GraphQl API.
I have a simple setup with vue-apollo below:
import Vue from 'vue'
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import VueApollo from 'vue-apollo'
import Cookies from 'js-cookie'
const httpLink = new HttpLink({
credentials: 'same-origin',
uri: 'http://localhost:8000/api/',
})
// Create the apollo client
const apolloClient = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
connectToDevTools: true,
})
export const apolloProvider = new VueApollo({
defaultClient: apolloClient,
})
// Install the vue plugin
Vue.use(VueApollo)
I also have CORS setup on my Django settings.py with the django-cors-headers package. All queries and mutations resolve fine when I use graphiQL or the Insomnia API client for chrome, but trying the mutation below from my vue app:
'''
import gql from "graphql-tag";
import CREATE_USER from "#/graphql/NewUser.gql";
export default {
data() {
return {
test: ""
};
},
methods: {
authenticateUser() {
this.$apollo.mutate({
mutation: CREATE_USER,
variables: {
email: "test#example.com",
password: "pa$$word",
username: "testuser"
}
}).then(data => {
console.log(result)
})
}
}
};
NewUser.gql
mutation createUser($email: String!, $password: String!, $username: String!) {
createUser (username: $name, password: $password, email: $email)
user {
id
username
email
password
}
}
returns with the error response below:
POST http://localhost:8000/api/ 400 (Bad Request)
ApolloError.js?d4ec:37 Uncaught (in promise) Error: Network error: Response not successful: Received status code 400
Regular queries in my vue app, however, work fine resolving the right response, except mutations, so this has me really baffled
400 errors generally mean there's something off with the query itself. In this instance, you've defined (and you're passing in) a variable called $username -- however, your query references it as $name on line 2.
In addition to graphiQL, I would like to add that apollo-link-error package would also had been of great help.
By importing its error handler { onError }, you can obtain great detail through the console about errors produced at network and application(graphql) level :
import { onError } from 'apollo-link-error';
import { ApolloLink } from 'apollo-link';
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
console.log('graphQLErrors', graphQLErrors);
}
if (networkError) {
console.log('networkError', networkError);
}
});
const httpLink = ...
const link = ApolloLink.from([errorLink, httpLink]);
const client = new ApolloClient({
...,
link,
...
});
By adding this configuration where you instantiate your Apollo Client, you would have obtained an error similar to this one:
GraphQLError{message: "Syntax Error: Expected {, found Name "createUser""}
Further information can be found in Apollo Doc - Error handling: https://www.apollographql.com/docs/react/features/error-handling.
Hope it helps in the future.
For me, it was the fact that I was using a field not defined in the GraphQL schema. Always be careful!
For sure the mutation is not formatted correctly if that is exactly what you are sending. You need an opening bracket in the mutation
mutation createUser($email: String!, $password: String!, $username: String!) {
createUser (username: $name, password: $password, email: $email) {
user {
id
username
email
password
}
}
}
With any of these queries when i run into bugs i paste it into either graphiql or graphql playground to identify what the formatting errors is in order to isolate what is wrong.
For people using laravel for backend, this helped me solve the problem
In the laravel project find file config/cors.php and change line 'paths' => ['api/*', 'sanctum/csrf-cookie'], to 'paths' => ['api/*', 'graphql', 'sanctum/csrf-cookie'],
Also in your vue app ensure that you're not using the no-cors mode in apollo config
Regards

State not changing when unit testing VueJS and VueResource

I'm trying to test a service that I've created that makes an API call with vue-resource. The service should make the call and update the component with the new data, however in my tests it doesn't register the updated value. I'm using the same setup as the vue-cli webpack example and have based my auth service off this repo (which unfortunately doesn't have any tests)
my service:
export default {
login(context, creds){
context.$http.post(LOGIN_URL, creds)
.then((response) => {
//do something else here
}, (response) => {
context.error = response.data.error
}
}
}
my test:
import Vue from 'vue'
import VueResource from 'vue-resource'
Vue.use(VueResource)
import Auth from 'src/auth'
describe('Auth', () => {
it('should throw an error on unsuccessful login', (done) => {
//intercept the api call and force an invalid response
Vue.http.interceptors.unshift((request, next) => {
next(request.respondWidth({error: 'some error'}, {status: 401}));
});
const vm = new Vue({
data: {
error: ''
}
}).$mount()
Auth.login(vm, {email: 'test#test.com', pass: 'test'} )
//this always fails
expect(vm.error).to.equal('some error')
//ive also tried:
vm.$nextTick(() => {
expect(vm.error).to.equal('some error')
//done()
});
//undo our interceptor
Vue.http.interceptors.shift()
}
}
When I run the test it fails because it's expecting '' to equal 'some error'.
My suspicions are around the fact that vue-resource is using promises.
After reading through some of the Vue.js tests I found my answer. Instead of using vm.$nextTick I did the following:
setTimeout(function(){
expect(vm.error).to.equal('something')
done()
}, 0)