Using es6 class to extend Axios - javascript

I'm interesting in creating an API wrapper and extending from axios using es6 classes. How is this possible? Axios has a method .create() which allows you to generate a new axios object
class Api extends Axios {
constructor(...args){
super(..args)
this.defaults.baseURL = 'https://api.com'
}
cancelOrder (id) {
return this.put(`/cancel/order/${id}`)
}
}
I know I have access to this let instance = axios.create().
Any thoughts?
Attempt 1
import axios from 'axios'
const Axios = axios.create()
class Api extends Axios {
constructor (...args) {
super(...args)
this.defaults.baseURL = 'https://api.com'
}
cancelOrder (id) {
return this.put(`/cancel/order/${id}`)
}
}
let api = new Api()
api.cancelOrder('hi')
.then(console.log)
.catch(console.log)
Attempt 2
import axios from 'axios'
class Axios {
constructor () {
return axios.create()
}
}
class Api extends Axios {
constructor () {
super()
this.defaults.baseURL = 'https://api.com'
}
cancelOrder (id) {
return this.put(`/cancel/order/${id}`)
}
}
let api = new Api()
console.log(api.__proto__)
api.cancelOrder('hi')
.then(console.log)
.catch(console.log)

axios currently does not currently export the Axios object it uses internally.
The .create() method only instantiates a new instance.
// Factory for creating new instances
axios.create = function create(defaultConfig) {
return new Axios(defaultConfig);
};
I created a pr that exports the Axios class.
https://github.com/reggi/axios/commit/7548f2f79d20031cd89ea7c2c83f6b3a9c2b1da4
And a github issue here:
https://github.com/mzabriskie/axios/issues/320

If you look at the source code of axios they do not seem to expose the "class" for Axios, only an instance.
I do not believe that an instance object can be extended in es6.
Your second attempt seems most viable, but if you want to emulate every single axios method, you may have a lot of overhead.

import axios, { Axios } from 'axios';
class Api extends Axios {
constructor () {
super()
this.defaults.baseURL = 'https://api.com'
}
cancelOrder (id) {
return this.put(`/cancel/order/${id}`)
}
}

you can install this package: npm i axios-es6-class

I also wanted to create class which would allow me to create multiple instances having predefined defaults. Here is my solution.
import axios from 'axios'
export class Axios {
constructor() {
return axios.create({
baseURL: 'http://127.0.0.1:8080/',
headers: {
Authorization: 'AUTH TOKEN FROM INSTANCE',
'Content-Type': 'application/json',
},
})
}
}
const db = new Axios()
db.get('/your_url').then().catch()

Well, many of the answers says that there's no class "Axios" exported from the axios package but that's not true atleast for version 0.26.0. Well if you want to create the Axios instance by yourself and customize it as you wish is pretty simple. I created a example using typescript feel free to use it if you want.
import { Axios, AxiosRequestConfig } from "axios";
class AxiosService<D = any> extends Axios {
constructor(config?: AxiosRequestConfig<D>) {
super(config);
this.defaults.baseURL = 'https://api.com'
this.interceptors.request.use(interceptorConfig => {
// Set up your default interceptor behavior here for requests, if you want
}
);
this.interceptors.response.use(
response => {
return response;
},
error => {
// Same thing here, set the behavior for onError responses
}
);
}
cancelOrder (id) {
return this.put(`/cancel/order/${id}`)
}
}
/*
Here you can choose between exporting the class or a instance of it
in my case i just want to export the instance with some default config
and use it, you may ask yourself why that's because i want to centrilize
the configs on one axios instance instead of multiple instances.
*/
const axiosInstance = new AxiosService({
// You may want to set this so axios automatically parse your data to a object.
transformResponse: res => {
return JSON.parse(res);
},
transformRequest: req => {
return JSON.stringify(req);
},
headers: {
"Access-Control-Allow-Origin": "true",
'Content-Type': 'application/json'
},
});
export { axiosInstance };
Disclaimer: think really well before going into this implementation because 99% of axios default configs, functions, helpers, etc will not be attached to this instance so you will have to insert them manually like we did when we instanciated a axiosService. But if all of axios stuff doesnt matter for you and you want to create a instance from scratch or even a basic instance to be used in some especific places feel free to do it. So most of times just import axios from "axios" and use axios.create.

Related

Cannot call store inside an API with Pinia

I'm using Vue 3.2 <script setup>, If I try to acess Pinia's store inside an API Service It throws the following error;
Uncaught ReferenceError: Cannot access 'store' before initialization at api.js?:9:1 (anonymous) # api.js?9:9
src/services/api.js:
import axios from 'axios';
import store from '../stores/index';
// eslint-disable-next-line no-undef
const api = axios.create({ baseURL: import.meta.env.VITE_APP_API_URL });
if (store) {
const { token } = store;
if (token) {
api.defaults.headers.Authorization = `Bearer ${token}`;
}
}
console.log(api);
export default api;
src/stores/index.ts:
import { defineStore } from 'pinia'
import Project from '../models/Project';
import { grantAuthSshd, revokeAuth, parseJwt } from '../services/auth';
const initialUser = JSON.parse(sessionStorage.getItem('Orcamento:token') || '{}');
const useProject = defineStore('project-store', {
state: () => ({
loading: false as boolean,
}),
actions: {
loadingDataTable(status: ((status: boolean) => void) & boolean) {
this.loadingDataTable = status;
},
}
});
I tried to use Pinia's interceptors but the error persists:
import axios from 'axios';
import useProject from '../stores/index';
const api = axios.create({ baseURL: import.meta.env.VITE_APP_API_URL });
// use interceptors
api.interceptors.request.use(
(config) => {
const { token } = store;
if ({token}) {
api.config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
const store = useProject();
export default api;
The problem is that there is indirect circular dependency between services/api.js and stores/index.ts modules, to the point they cannot be evaluated correctly.
useProject() returns a singleton, one of reasons why a store is wrapped with a function is that this prevents it from being accessed too soon. Pinia stores are supposed to be accessed only after Pinia is initialized, otherwise this would require to evaluate the modules that depend on it in a specific order that isn't easy to achieve.
In this case useProject is supposed to be used in-place, not on module evaluation:
api.interceptors.request.use(
(config) => {
const store = useProject();
const { token } = store;
...
Due to how ES modules work, this allows to resolve circular dependency.
A way to avoid circular dependency is to move this code from services/api.js to another module that stores/index.ts doesn't depend on, e.g. entry point.

Axios client with useSwr Fails to make request

Im using a shared Axios "client" object that is passed around my application via react context, this client object has the API key auth header and base paths already configured so im not constantly defining it.
My problem is trying to use the useSwr hook, specifically when defining the fetcher. I just cannot get it to work, and im sure im missing something simple here.
Basically, I pull the api client off the context, and use a fetcher function iv defined already, but I get nothing.
Here's some snips,
The Client
const AXIOS_CLIENT_CONFIG = {
baseURL: API_BASE,
timeout: 2000,
};
export default class APIClient {
client: AxiosInstance;
accessToken: string;
headers: any;
constructor(accessToken?: string) {
this.accessToken = accessToken;
this.headers = { Authorization: `Bearer ${accessToken}` };
if (accessToken) {
this.client = axios.create({
...AXIOS_CLIENT_CONFIG,
headers: this.headers,
});
} else {
this.client = axios.create(AXIOS_CLIENT_CONFIG);
}
}
fetcher(url: string): Promise<any> {
return this.client.get(url).then((res) => res.data);
}
The Component
export default function Upload(): ReactElement {
const { api }: IAppContext = useContext(AppContext);
const { data, error } = useSwr(`/upload/${uploadId}`, api.fetcher, {
refreshInterval: 5000,
});
Using above, I see nothing, no requests, no errors. (yes, the client comes through fine, I use this throughbout my whole app, its just this fetcher part that is broken)
Just for testing if I define the following fetcher, I can see a request is made (and failed due to auth)
const fetcher = (url) => axios.get(url).then((res) => res.data);
Even logging out the function signatures, they look almost the same to me
console.log("API FETCHER", api.fetcher);
console.log("NORMAL FETCHER", fetcher);
Outputs
API FETCHER ƒ fetcher(url) {
return this.client.get(url).then(function (res) {
return res.data;
});
}
NORMAL FETCHER ƒ fetcher(url) {
return axios__WEBPACK_IMPORTED_MODULE_5___default().get(url).then(function (res) {
return res.data;
});
}
What am I doing wrong here?
After hours of screwing around, I eventually figured this out. Incase anyone else comes across the issue when trying to use an Axios client objection with class functions like I am here.
I had no bound the context of this within the class for that specific function.
Basically, I needed to add the following to my api clients constructor
// Bind "this" context
this.fetcher = this.fetcher.bind(this);

Why is axios returning a wrap function?

axios is returning wrap function but I want to use axios.CancelToken in it. Any idea on why this is happening?
import axios from "axios";
const instance = axios.create({
baseURL: appUrl,
timeout: 0,
});
instance.interceptors.request.use(
function(config) {
console.log(instance);
}
);
export const $axios = instance;
CancelToken is a static property of axios. It's not an instance property.
If you're using ES6 module imports, an easy way to reference it is by importing it
import axios, { CancelToken } from "axios"
const instance = axios.create({
baseURL: appUrl,
timeout: 0,
});
const source = CancelToken.source()
instance.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// cancel the request (the message parameter is optional)
source.cancel("Operation cancelled by the user.");
This is really just a shorter version of
import axios from "axios"
const CancelToken = axios.CancelToken

Unable to use an axios plugin in nuxt

I'm attempting to add an Axios plugin to Nuxt as described here, but it doesn't seem to work.
This is my plugins/axios.js file...
export default function({ $axios }) {
console.log('Im in the axios plugin')
$axios.defaults.baseURL = `https://localhost:5001/api`
$axios.defaults.headers = {
Accept: 'application/json',
'Content-Type': 'application/json'
}
$axios.onRequest((config) => {
console.log('Making request to ' + config.url)
})
}
This is my nuxt.config.js
plugins: ['~/plugins/axios'],
modules: ['#nuxtjs/axios']
And this is where I use Axios in a file called services/BookService.js:
import axios from 'axios'
export default {
getBooks() {
return axios.get('/Home')
},
getBooksFiltered(payload) {
return axios.post('/Home/Filters', payload)
}
}
I get the console.log('Im in the axios plugin') from within my plugin, but nothing else. $axios.onRequest doesn't appear to run, and the baseURL doesn't appear to be set correctly when getBooksFiltered is triggered. I get a 404 when it tried to hit the address http://localhost:3000/Home/Filters. As described in my plugin, the address should be https://localhost:5001/api/Home/Filters
I've also tried the following in my nuxt.config.js, but it doesn't work:
axios: {
baseURL: 'https://localhost:5001/api'
}
Any ideas?
Edit
I've modified my services/BookService.js based on the suggestion below to the following...
export default {
getBooks(axios) {
console.log('Im in getBooks')
return axios.get('/Home')
}
}
My action request that makes my api call is the following....
import BookService from '~/services/BookService.js'
export const fetchBooks = (context) => {
console.log('Im in fetchBooks action')
return BookService.getBooks(this.$axios)
.then((response) => {
context.commit('SET_BOOKS', response.data.booksList)
})
.catch((error) => {
console.log(error)
})
}
And my method in my component that calls the actions...
async fetch({ store, error }) {
try {
console.log('Im in index -> fetch')
await store.dispatch('fetchBooks')
} catch (e) {
error({
statusCode: 503,
message: 'Unable to fetch books at this time'
})
}
}
I'm aware that I may be mixing async/await with promises incorrectly but I don't believe it's the cause of this issue.
Console returns the following...
My network tab contains a single request to http://localhost:3000/ which seems incorrect. It should be https://localhost:5001/api/Home based on the plugin and the address specified in the action. It is also never entering $axios.onRequest
The axios-module sets up an Axios instance on the Nuxt app instance. When you import Axios from axios, and use it directly, you're not using the previously setup Axios instance.
To fix the issue, you could either reference the preconfigured Axios instance from window.$nuxt.$axios (only in the browser), or setup your service to take an Axios instance as a parameter:
// services/BookService.js
export default axios => ({
getBooks() {
return axios.get('/Home')
},
getBooksFiltered(payload) {
return axios.post('/Home/Filters', payload)
}
})
// store.js
import BookService from '~/services/BookService.js'
export default {
actions: {
async getBooks({ commit }) {
const books = await new BookService(this.$axios).getBooks()
commit('SET_BOOKS', books)
}
}
}
Another solution from nuxt-community/axios-module #28:
~/plugins/axios-port.js
import { setClient } from '~/services/apiClient'
export default ({ app, store }) => {
setClient(app.$axios)
}
~/services/apiClient.js
let client
export function setClient (newclient) {
client = newclient
}
// Request helpers
const reqMethods = [
'request', 'delete', 'get', 'head', 'options', // url, config
'post', 'put', 'patch' // url, data, config
]
let service = {}
reqMethods.forEach((method) => {
service[method] = function () {
if (!client) throw new Error('apiClient not installed')
return client[method].apply(null, arguments)
}
})
export default service
Use:
import apiClient from '~/services/apiClient'
export default {
async current () {
return apiClient.get('...')
}
}
In my case I exported a customized axios instance as the doc suggested in my axios.js
export default function ({ $axios }, inject) {
const api = $axios.create({
baseURL:'/api'
})
// Inject to context as $api
inject('api', api)
}
Then use this.$api.get or this.$api.post in your getBook service
The above one works for me
As I have just tested, in each request we should use $axios.
Example: this.$axios.get('....'), or in another context this.$nuxt.$axios.get('...');
Because axios extension use with the app context instance, if we import, it will create a new instance which plugin cannot extend.
I have put test code on stackblitz: here
It seems you need to yarn add #nuxtjs/axios or npm install #nuxtjs/axios like the setup instruction here before it can work: https://axios.nuxtjs.org/setup
I haven't experienced with nuxt yet but I don't think by adding some line of code into some js file without actually installing will make the package available into your repo.

how to modularize rest api calls using axios in vuejs

I am creating a vuejs app where I am using axios to consume my rest api.
I am basically calling axios.get in various places, every time creating a new instance with the required authentication headers.
// UserdataComponent.vue
const anInstance = axios.create({
headers: {'X-API-TOKEN': store.state.token},
auth: {
username: SOME_USERNAME,
password: SOME_PASSWORD
}
})
anInstance.get(API_BASE_URL + '/userdata')
This is being done everywhere I make a rest api call.
So I wanted to move this to a separate file to keep the code DRY.
I moved the axios instance creation code to a separate file and tried exporting it as an object. This object can then be imported wherever I want to consume rest api.
I was expecting something like this to work....
// http.js
import axios from 'axios'
import store from 'store/store.js'
const HTTP = axios.create({
baseURL: API_BASE_URL,
headers: { 'X-API-TOKEN': store.state.token },
auth: {
username: SOME_USERNAME,
password: SOME_PASSWORD
}
})
export default HTTP
// UserdataComponent.vue
import HTTP from 'http.js'
...
HTTP.get('/userdata')
This gave me errors of all sorts with axios.create being returned as a string, instead of a callable function.
What should I be changing here to make it work as I want to? Should I even be using this way to modularize the http request mechanism?
Not sure if this answers you question but it's a nice way of setting it up.
If you create the axios instance in a separate file, you could export specific api calls instead, making them accessible for other components as well.
// api.js
const HTTP = axios.create({
baseURL: API_BASE_URL,
headers: { 'X-API-TOKEN': store.state.token },
auth: {
username: SOME_USERNAME,
password: SOME_PASSWORD
}
})
export default {
getSomeData () {
return HTTP.get('/endpoint')
},
postSomeData (id, name) {
return HTTP.post('/endpoint', {
id: id,
name: name
})
}
}
and then, in your component you import the api.jsand use like this:
//component.vue
import myApi from '../path/to/api'
export default {
name: 'myComponent',
methods: {
someMethod () {
myApi.getSomeData().then((response) => {
...code
})
}
}
}
I think you should use axios interceptors for this:
Axios.interceptors.request.use((config) => {
// Add stuff to the config..
// Add credentials to each request.
config.withCredentials = true
config.timeout = 10000
config.headers['Accept-Language'] = i18n.locale
config.headers['Content-Type'] = 'application/json'
return config
})
You can place this code in your main file.
Each time you do a request, this code is called and you can add your credentials to the request, so you don't have to pass the same code everywhere...
For more information check https://github.com/axios/axios#interceptors and on the web around this subject.

Categories