Why use Next.js API route with an external API? - javascript

I am new in Next.js.
I want to know what is the use of export default function handler because we can directly call the API using fetch.
In my HTML code I put below code. When I click on submit button sendformData() function will be called.
<input type="button" value="Submit" onClick={() => this.sendformData()} ></input>
sendformData = async () => {
const res = await fetch("/api/comments/getTwitFrmUrl?twitUrl=" + this.state.twitUrl, {
headers: {
"Content-Type": "application/json",
},
method: "GET",
});
const result = await res.json();
this.setState({ data: result.data });
};
When sendformData function is called, it calls /api/comments/ file and calls the function.
Here is the /api/comments/[id].js file code.
export default async function handler(req, res) {
if (req.query.id == 'getTwitFrmUrl') {
const resData = await fetch(
"https://dev.. .com/api/getTwitFrmUrl?twitId=" + req.query.twitUrl
).then((response) => response.text()).then(result => JSON.parse(result).data);
res.status(200).json({ data: resData });
}
else if (req.query.id == 'getformdata') {
console.log('getformdata api');
res.status(200).json({ user: 'getuserData' });
}
}
When I put the below code in the sendformData same response will be retrieved. So why we need to call
export default function handler function?
sendformData = async () => {
const res = await fetch(
"https://dev.. .com/api/getTwitFrmUrl?twitId=" + req.query.twitUrl
).then((response) => response.text()).then(result => JSON.parse(result).data);
const result = await res.json();
this.setState({ data: result.data });
};

If you already have an existing API there's no need to proxy requests to that API through an API route. It's completely fine to make a direct call to it.
However, there are some use cases for wanting to do so.
Security concerns
For security reasons, you may want to use API routes to hide an external API URL, or avoid exposing environment variables needed for a request from the browser.
Masking the URL of an external service (e.g. /api/secret instead of https://company.com/secret-url)
Using Environment Variables on the server to securely access external services.
— Next.js, API Routes, Use Cases
Avoid CORS restrictions
You may also want to proxy requests through API routes to circumvent CORS. By making the requests to the external API from the server CORS restrictions will not be applied.

Related

Can't get json with axios.get and headers

I am trying to get the joke from https://icanhazdadjoke.com/. This is the code I used
const getDadJoke = async () => {
const res = await axios.get('https://icanhazdadjoke.com/', {headers: {Accept: 'application/json'}})
console.log(res.data.joke)
}
getDadJoke()
I expected to get the joke but instead I got the full html page, as if I didn't specify the headers at all. What am I doing wrong?
If you look at the API documentation for icanhazdadjoke.com, there is a section titled "Custom user agent." In that section, they explain how they want any requests to have a User Agent header. If you use Axios in a browser context, the User Agent is set for you by your browser. But I'm going to go out on a limb and say that you are running this code via Node, in which case, you may manually need to set the User Agent header, like so:
const getDadJoke = async () => {
const res = await axios.get(
'https://icanhazdadjoke.com/',
{
headers:
{
'Accept': 'application/json',
'User-Agent': 'my URL, email or whatever'
}
}
)
console.log(res.data.joke)
}
getDadJoke()
The docs say what they want you to put for the User Agent, but I think it would honestly work if there were any User Agent field at all.
The HTML page you're getting is a 503 response from Cloudflare.
As per the API documentation
Custom user agent
If you intend on using the icanhazdadjoke.com API we kindly ask that you set a custom User-Agent header for all requests.
My guess is they have a Cloudflare Browser Integrity Check configured that's triggering for the default Node / Axios user-agent.
Setting a custom user-agent appears to get around this...
const getDadJoke = async () => {
try {
const res = await axios.get("https://icanhazdadjoke.com/", {
headers: {
accept: "application/json",
"user-agent": "My Node and Axios app", // use something better than this
},
});
console.log(res.data.joke);
} catch (err) {
console.error(err.response?.data, err.toJSON());
}
};
Given how unreliable Axios releases have been since v1.0.0, I highly recommend you switch to something else. The Fetch API is available natively in Node since v18
const getDadJoke = async () => {
try {
const res = await fetch("https://icanhazdadjoke.com/", {
headers: {
accept: "application/json",
"user-agent": "My Node and Fetch app", // use something better than this
},
});
if (!res.ok) {
const err = new Error(`${res.status} ${res.statusText}`);
err.text = await res.text();
throw err;
}
console.log((await res.json()).joke);
} catch (err) {
console.error(err, err.text);
}
};
Using Axios REST API call which response JSON format.
If you using API from https://icanhazdadjoke.com/api#authentication
, you can use Axios.
Here is example.
Alternative method.
You needs to use web scrapping method for this case. Because HTML response from https://icanhazdadjoke.com/.
This is example how to scrap using puppeteer library in node.js
Demo code
Save as get-joke.js file.
const puppeteer = require("puppeteer");
async function getJoke() {
try {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://icanhazdadjoke.com/');
const joke = await page.evaluate(() => {
const jokes = Array.from(document.querySelectorAll('p[class="subtitle"]'))
return jokes[0].innerText;
});
await browser.close();
return Promise.resolve(joke);
} catch (error) {
return Promise.reject(error);
}
}
getJoke()
.then((joke) => {
console.log(joke);
})
Selector
Main Idea to use DOM tree selector
In the Chrome's DevTool (by pressing F12), shows HTML DOM tree structures.
<p> tag has class name is subtitle
document.querySelectorAll('p[class="subtitle"]')
Install dependency and run it
npm install puppeteer
node get-joke.js
Result
You can get the joke from that web site.

Next js API + MongoDB error 431 on dynamic request

I'm getting a 431 (headers fields too large) on some API calls within a fullstack Next JS project. This only occurs on a dynamic API route (/author/get/[slug]), same result with both frontend and Postman. The server is running on local, and other endpoints works fine with exactly the same fetching logic.
The request is not even treated by Next API, no log will appear anywhere.
The database used is mongoDB. The API is pure simple JS.
The objective is to get a single author (will evolve in getStaticProps)
The API call looks like this (no headers whatsoever):
try {
const res = await fetch(`http://localhost:3000/api/author/get/${slug}`, { method: "GET" })
console.log(res)
} catch (error) { console.log(error) }
And the endpoint:
// author/get/[slug].js
import {getClient} from "../../../../src/config/mongodb-config";
export default async function handler(req, res) {
const { query } = req
const { slug } = query
if(req.method !== 'GET') {
return
}
const clientPromise = await getClient()
const author = clientPromise.db("database").collection("authors").findOne({ 'slug': slug })
res.status(200).json(author)
await clientPromise.close()
}
Tried without success:
To remove a nesting level (making the path /author/[slug])

Next js data fetching with axios instance and Authorization

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.

Next.js getInitialProps cookies

I have encountered an issue regarding fetching data from the getInitialProps function in Next.js
The scenario is this: when a user first visits a page, I make an HTTP request to a distant API which returns me data that I need for the application. I make the request inside the getInitialProps method because I want the content to be fully rendered when I ship the content to the user.
The problem is, when I make this request, the API returns me a session cookie which I need to store inside the browser, not the server that is rendering the content. This cookie will have to be present inside future client-side requests to the API. Otherwise, the API returns me 403.
My question is: If I'm performing this request from the server, and because of that the response also comes back to the server, How can I set the cookie for the browser so that I could make client-side requests to the API?
I tried manipulating the domain option of the cookie but I cannot set another domain. The browser just ignores it.
Here is how my getInitialProps looks like:
static async getInitialProps(appContext) {
const { Component, ctx, router } = appContext;
const { store } = ctx;
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(appContext);
}
const { hotelId, reservationId } = router.query;
if (!hotelId || !reservationId) return { pageProps };
// Fetching reservation and deal data
try {
const { data, errors, session } = await fetchData(hotelId, reservationId);
if (data) {
store.dispatch(storeData(data));
}
// This works, but the domain will be the frontend server, not the API that I connecting to the fetch the data
if (session) {
ctx.res.setHeader('Set-Cookie', session);
}
// This doesn't work
if (session) {
const manipulatedCookie = session + '; Domain: http://exampe-api.io'
ctx.res.setHeader('Set-Cookie', manipulatedCookie);
}
if (errors && errors.length) {
store.dispatch(fetchError(errors));
return { errors };
} else {
store.dispatch(clearErrors());
return {
...pageProps,
...data
};
}
} catch (err) {
store.dispatch(fetchError(err));
return { errors: [err] };
}
return { pageProps };
}
The fetchData function is just a function which sends a request to the API. From the response object, I'm extracting the cookie and then assign it to the session variable.
getInitialProps is executed on the client and server. So when you write your fetching function you have fetch conditionally. Because if you make request on the server-side you have to put absolute url but if you are on the browser you use relative path. another thing that you have to be aware, when you make a request you have to attach the cookie automatically.
in your example you are trying to make the request from _app.js. Next.js uses the App component to initialize the pages. So if you want to show some secret data on the page, do it on that page. _app.js is wrapper for all other components, anything that you return from getInitialProps function of _app.js will be available to all other components in your application. But if you want to display some secret data on a component upon authorization, i think it is better to let that component to fetch the data. Imagine a user logins his account, you have to fetch the data only when user logged in, so other endpoints that does not need authentication will not access to that secret data.
So let's say a user logged in and you want to fetch his secret data. imagine you have page /secret so inside that component I can write like this:
Secret.getInitialProps = async (ctx) => {
const another = await getSecretData(ctx.req);
return { superValue: another };
};
getSecretData() is where we should be fetching our secret data. fetching actions are usually stored in /actions/index.js directory. Now we go here and write our fetching function:
// Since you did not mention which libraries you used, i use `axios` and `js-cookie`. they both are very popular and have easy api.
import axios from "axios";
import Cookies from "js-cookie";
//this function is usually stored in /helpers/utils.js
// cookies are attached to req.header.cookie
// you can console.log(req.header.cookie) to see the cookies
// cookieKey is a param, we pass jwt when we execute this function
const getCookieFromReq = (req, cookieKey) => {
const cookie = req.headers.cookie
.split(";")
.find((c) => c.trim().startsWith(`${cookieKey}=`));
if (!cookie) return undefined;
return cookie.split("=")[1];
};
//anytime we make request we have to attach our jwt
//if we are on the server, that means we get a **req** object and we execute above function.
// if we do not have req, that means we are on browser, and we retrieve the cookies from browser by the help of our 'js-cookie' library.
const setAuthHeader = (req) => {
const token = req ? getCookieFromReq(req, "jwt") : Cookies.getJSON("jwt");
if (token) {
return {
headers: { authorization: `Bearer ${token}` },
};
}
return undefined;
};
//this is where we fetch our data.
//if we are on server we use absolute path and if not we use relative
export const getSecretData = async (req) => {
const url = req ? "http://localhost:3000/api/v1/secret" : "/api/v1/secret";
return await axios.get(url, setAuthHeader(req)).then((res) => res.data);
};
this is how you should implement fetching data in next.js

How do I pass a react DOM state's information to backend(NodeJS) when a button is invoked?

I'm using his logic on the frontend, but I'm having some trouble actually receiving that data on the backend. I'm using the Sails.js framework. Any suggestions?
handleSubmit = () => {
// Gathering together the data you want to send to API
const payload = {
subject: this.state.subject,
message: this.state.message,
};
this.handleAjaxRequest(payload);
};
// Method to send data to the backend
// Making the req -I'm using Axios here.
handleAjaxRequest = (payload) => {
let request = axios({
method: 'post',
url: '/api/',
data: payload,
headers: 'Content-Type: application/json'
});
// Do stuff with the response from your backend.
request.then(response => {
console.debug(response.data);
})
.catch(error => {
console.error(error);
})
};
I used to do this using Express and didn't have these problems.
Any help, method, a suggestion is more than welcome :)
Please forgive my ignorance, I'm just here to learn.
Okay, so the first thing I had to do is generate a new restful API using the command sails generate api data. In the package.json file I set up a proxy that includes the backends endpoint, like this "proxy": "http://localhost:1337" - I mean, you don't need to do this, but if you don't then you have to include this URL part on every request. Because it doesn't change, it's pretty convenient to do so.
On the frontend, I made a function sendData() that takes the necessary data from my previous component (depending on what the user selected) and send that data using axios to the backend -->
sendData = () => {
const { relYear } = this.props.history.location.state.dev;
const { relMonth } = this.props.history.location.state.dev;
const selectedMonth = moment().month(relMonth).format("MM");
const finalSelect = parseInt(relYear + selectedMonth, 10);
axios.post('/data', { 'selectedDate' : finalSelect })
.then(res => console.log('Data send'))
.catch(err => console.error(err));
}
On the backend I fetched the data, did the calculations and send back the result to the frontend of my app. -->
getApiData = () => {
let apiData = [];
axios.get('/data')
.then(res => {
let first = Object.values(res.data.pop()).shift(); // Getting the relevant 'selectedDate'
apiData.push(first);
}).catch(err => console.error(err));
return apiData;
}

Categories