Next js data fetching with axios instance and Authorization - javascript

I'm using NextJS 12.0.10 with next-redux-wrapper 7.0.5
And Axios custom instance to hold user JWT token saved in local storage and inject it with every request also to interceptors incoming error's in each response
The problem with this is that I simply cannot use the Axios instance inside the Next data fetching methods
Because there is no way to bring user JWT Token from local storage when invoking the request inside the server
Also, I cannot track the request in case of failure and send the refresh token quickly
I tried to use cookies but getStaticProps don't provide the req or resp obj
Should I use getServerSideProps always
axios.js
const axiosInstance = axios.create({
baseURL: baseURL,
timeout: 20000,
headers: {
common: {
Authorization: !isServer()
? localStorage.getItem("access_token")
? "JWT " + localStorage.getItem("access_token")
: null
: null,
accept: "application/json",
},
},
});
login-slice.js
export const getCurrentUser = createAsyncThunk(
"auth/getCurrentUser",
async (_, thunkApi) => {
try {
const response = await axiosInstance.get("api/auth/user/");
await thunkApi.dispatch(setCurrentUser(response.data));
return response.data;
} catch (error) {
if (error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
toast.error(error.message);
return thunkApi.rejectWithValue(error.message);
}
}
);
Page.jsx
export const getStaticProps = wrapper.getStaticProps((store) => async (ctx) => {
try {
await store.dispatch(getCurrentUser());
} catch (e) {
console.log("here", e);
}
return {
props: {},
};
});

Server side rendered technology is a one-way street if you follow the standard practise. You won't get any local details - being it cookies, local store or local states back to the server.
I would let the server build the DOM as much as it makes sense (ie with empty user data) and let the client fetch the data via useEffect.

Related

How to use same token until expires?

In the code below, I will call LoginAPI for authorization and writes token as a state(Login.e2e.ts ).By the way using axios interceptors.request in my axios.ts file.
My question is;
If I use below code logic, when I send request with customAxios in my project,everytime await LoginAPI.API.Signin.run() will run for every API request. Soon,I can have 100 API call. I don t want run every time await LoginAPI.API.Signin.run(),because I can take 429 error.
The new logic should be like this;
I want to take a token first and then use it until it expires. If the token expired then send a new request and get a new token. How can do this using JavaScript or TypeScript?
This is my Login.e2e.ts file
import api from "api/core"
import { expect } from "#playwright/test";
export const LoginAPI = {
States: {
token: {} as string
},
API: {
Signin: {
notes: "user login",
run: async () => {
let res: any = await api.test.LoginPost(process.env.NAME, process.env.PASS)
LoginAPI.States.token = res.data.token
expect(res.status).toBe(200)
},
},
},
};
This is my axios.ts file
import axios from "axios";
import { LoginAPI } from "../playwright/tests/login/login.api";
const customAxios = axios.create({
baseURL: process.env.ENV === '1' ? "https://test1" : process.env.ENV === '2' ? "https://test2" : "https://test3",
});
customAxios.interceptors.request.use(
async (config) => {
await LoginAPI.API.Signin.run()
if (config.headers) {
config.headers['Authorization'] = `Bearer ${LoginAPI.States.token}`;
return config;
}
return config;
},
(error) => {
Promise.reject(error);
}
);
export default customAxios
take a token first and then use it until it expires. If the token expired then send a new request and get a new token.The above code should be changed the this logic
I would suggest you to log in once and get cookies from the browser.context() and save them as JSON file then use this cookie state/session for the rest of the test. That way you won't have to log in every time for new tests or test suites.
More information using storageState(options) here in official docs.
Example of using storageState(options) in your global setup and teardown here in official docs.

getServerSideProps timed out function in production, vercel consuming express api and mongodb

This question is more about how Vercel serverless functions work. I have requested an express.js with MongoDB and taking around 0.078 seconds to at most 1.856 seconds.
getServerSideProps works perfect on LocalHost. However, whenever I use the getServerSideProps, I get the error
[GET] /articles/637a08a20218e2e3c841e8d7
22:58:07:13
2022-11-20T20:58:17.290Z 881d4090-532d-42df-9ee2-dd759a6eae08 Task timed out after 10.02 seconds
Of course, getStaticPaths and getStaticProps works perfectly but I'll be having around 100000 dynamic routes to create which isn't scalable. I'm already using SWR for index pages (list of articles with pagination) but I can't use it for dynamic pages because it's bad for SEO
My question is, what's the cause of this timeout?
API: Express hosted on AWS Elastic Beanstalk, MongoDB Atlas serverless
Next.js
// [articleID].tsx
export const getServerSideProps: GetServerSideProps = async (context) => {
const { article } = context.params;
const article = await getFetcher(`/articles/${articleID}`);
if (!article) {
return {
notFound: true,
};
}
return {
props: { article },
};
};
Reusable fetch
async function getFetcher(path: string) {
if (!path) return;
const url = `${API_BASE_URL}${path}`;
// headers
const headers = {
Authorization: AUTHORIZED_ACCESS_TOKEN,
"Content-type": "application/json",
};
const config = { headers };
const response = await axios
.get(url, config)
.then((res) => res.data)
.catch((error) => {
console.log("GET Resource Error");
console.log(error);
return { success: false, error, message: error.message };
});
return response;
}

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

Loading headers from SecureStore in apisauce

All the endpoints in the backend require Authorization header. This header is stored in SecureStore.
Problem Statement
I want to load the Authorization header ( JWT Token ), for every API call after logging in.
Now this requires an async operation i.e.authStorage.getToken.
This is my client.js ( the apisauce client ).
client.js
import { create } from "apisauce";
import authStorage from "../auth/storage";
import IP from "../config/network";
const restoreToken = async () => {
return await authStorage.getToken("idToken");
};
const apiClient = (auth_token = "") =>
create({
baseURL: "http://" + IP + ":8990",
headers: { Authorization: auth_token }, // This I've added later
});
export default apiClient;
This is the PostsApi which uses apiClient to make the calls.
PostsApi.js
import apiClient from "./client";
const endpoint = "/api/";
const bookmarkEndpoint = "/bookmark/";
const getPosts = (last_id = 0, limit = 10) => {
return apiClient.get(endpoint + "?last_id=" + last_id + "&limit=" + limit);
};
const toggleBookmark = (item_id) => {
return apiClient.get(bookmarkEndpoint + "?item_id=" + item_id);
};
export default {
getPosts,
toggleBookmark,
};
My Understanding
I understand that if I can add the header in client.js itself, it would be injected everytime there's an API call.
I've tried :
const restoreToken = async () => {
return await authStorage.getToken("idToken");
};
But I am not sure how to call this async operation in client.js
Bonus Question
This token ( idToken ) would be reloaded every hour, so it's best to get the token from SecureStore everytime instead of saving it once.
Thanks.
Accepted answer and what worked for me
Worked for me
apisauce's setHeader : Documented here
Accepted answer is a detailed drilling of the axios setting up of headers. So if someone's using axios client directly they can see the accepted answer else, if you're an apisauce user, use the setHeader functionality provided with the library.
Cheers.
You will have to store your token with the state (can be redux or local state).
During save/refresh/reload the token, you will have set headers of the HTTP client.
You can set header using below command (example)
export const setAuthToken = (token) => {
apiClient.defaults.headers.common['Authorization'] = ''
delete apiClient.defaults.headers.common['Authorization']
if (token) {
apiClient.defaults.headers.common['Authorization'] = `Bearer ${token}`
}
}
Call the above function to set a token during reload/refresh/creation of token.
const restoreToken = async () => {
return await authStorage.getToken("idToken").then(token => setAuthToken(token));
};

How to use apollo graphql (react) in a js function in react native?

This is how my simple function to synchronize the data looks like:
Function
import { getData } from './api/index'
export default async function synchronize (navigator) {
const data = await getData()
// ... then store data to local db...
}
I'm fetching some data from the server using an RESTful API:
getData
import { Alert, AsyncStorage } from 'react-native'
async function getData () {
try {
const lastSynched = await AsyncStorage.getItem('data.lastSynched')
const date = lastSynched ? Number(Date.parse(lastSynched)) / 1000 : 0
const token = await AsyncStorage.getItem('auth.token')
const uriBase = 'http://localhost:3000'
let response = await fetch(`${uriBase}/get-data/${date}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'x-access-token': token
}
})
let responseJson = await response.json()
return responseJson
} catch (error) {
Alert.alert('Error', 'Could not synchronize data')
}
}
export default getData
But now I'm using apollo graphQL and I do not understand how to get the data using a query as I'm using here a function (synchronize()) - not a component.
I think good start will be from this link. Here you have good examples how to use Apollo client to execute query and fetch data.
Maybe I don't understand properly what is issue but here is high level of Apollo usage.
First you will need to create Apollo client and supply at least URI to GraphQL endpoint.
import ApolloClient from "apollo-boost";
const client = new ApolloClient({
uri: "https://w5xlvm3vzz.lp.gql.zone/graphql"
});
Once you created client you should than execute your query with previously created client like in following:
import gql from "graphql-tag";
client
.query({
query: gql`
{
rates(currency: "USD") {
currency
}
}
`
})
.then(result => console.log(result));
Make sure that you installed apollo-boost react-apollo graphql-tag graphql packages. Also make sure that you wrap your query into GraphQL tag like this because it will compile your query.

Categories