how to modularize rest api calls using axios in vuejs - javascript

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.

Related

Sveltekit POST method not allowed on endpoint

I am trying to get this fetch method to work in Svelte
When the page.svelte calls the function to fetch the data, in the console I receive this
[HTTP/1.1 405 Method Not Allowed 19ms]
Which I have narrowed down to this
POST method not allowed
Is there a variable I need to set in the config files or is there another solution which I am missing?
// svelte.config.js
import adapter from '#sveltejs/adapter-auto';
const config = {
kit: {
adapter: adapter(),
methodOverride: {
allowed: ['POST']
},
}
};
export default config;
Both files are in the Routes folder
// fetch-data.js
export const POST = async(data) => {
const response = // get data function here
return {
body: {
data: response
}
}
}
// page.svelte
async function fetchData() {
const response = await fetch('./fetch-data', {
method: 'POST',
body: JSON.stringify(),
headers: {
'content-type': 'application/json'
}
})
const { data } = await response.json()
return data
}
The solution was to change the POST variable name to lowercase in the get-data.js file
I had the same error for a different reason, and the syntax in your question and answer has changed again so I'll add my discoveries:
+page.server.ts should no longer export const POST but instead export const actions: Actions = { ... } which don't need to return anything.
+server.ts still uses function POST(event: RequestEvent) and needs to return a Response (from the Fetch API).
Cookies can no longer be set with setHeaders but must use cookies instead. Otherwise the handler also returns 405.
I had the same problem because of old SvelteKit version (I used .456, current version at the time this was written is .484). So make sure you have the most recent version of framework, when referring to the docs ;)

How to convert Angular clientHttp to node.js

I have the following code that works in Angular and wish to convert it to node can someone plase help?
import { HttpClient, HttpErrorResponse, HttpHeaders } from '#angular/common/http';
private getDeviceIP(){
this.clientHttp.get("http://api.ipify.org/?format=json").subscribe((res:any)=>{
this.userIpAddress = res.ip;
});
}
Cheers
If you want to use that logic in NodeJS be aware that NodeJS server is the one that will send the request, so you will got his IP address, and not IP address of the client.
If you still want to implement the same logic in NodeJs, you can use axios (Promise based HTTP client), like this:
const axios = require('axios');
const getDeviceIP = (req, res, next) => {
axios({
method: 'get',
url: 'http://api.ipify.org/?format=json'
}).then((response) => {
return res.status(200).json({ success: true, ip_address: response.data.ip })
}).catch((error) => {
return res.status(400).json({ success: false, error })
});
}
Depending on your use-case (see caveat below), this is already converted to Node!
If you use the #angular/common, typescript and xmlhttprequest packages, you can use the following:
import { XMLHttpRequest } from 'xmlhttprequest';
import { HttpClient, HttpXhrBackend } from '#angular/common/http';
// Your unmodified class
class Main {
userIpAddress: any;
constructor (private clientHttp: HttpClient) {}
getDeviceIP () {
this.clientHttp.get('http://api.ipify.org/?format=json').subscribe((res: any) => {
this.userIpAddress = res.ip;
});
}
}
// Manually perform the dependency resolution
const backend = new HttpXhrBackend({
build: (): XMLHttpRequest => new XMLHttpRequest()
});
const client = new HttpClient(backend);
const main = new Main(client);
// Run it!
main.getDeviceIP();
which can be transpiled with:
npx tsc --lib dom,es2015 main.ts
and run with:
node main.js
Caveat
Note the use of dom as a library in transpilation; this is required because the #angular/common/http subpackage isn't properly decoupled from the DOM related parts of #angular/core. For the purposes of making requests with a HttpClient with a Node compatible backend, this is fine, but attempting to convert more DOM related tasks will likely run into issues.

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);

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.

Using es6 class to extend Axios

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.

Categories