Below logic is working. I need it in my server file in React app. How do I extract this (or the most of it) to its own file, like redirectHandler.ts
?
if (req.url.includes('foo')) {
const GET_ID = `query ($url: String!) {
page (url: $url){
content {
...on Page {
id
}
}
}
}`;
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: GET_ID,
variables: {
targetUrl: req.originalUrl,
},
}),
};
const getData = async () => {
const response = await fetch(process.env.ENDPOINT, options);
const json = response.json();
return json;
};
try {
const result = await getData();
res.redirect(`/my-page/?id=${result.data.page.content.id}`);
} catch (err) {
console.error(err);
}
return;
}
I tried the following:
I create a file redirectHandler.ts
:
import * as express from 'express';
import GET_ID from './videoRedirectHandler.gql';
const videoRedirectHandler = async (req: express.Request, res: express.Response) => {
if (req.url.includes('foo')) {
const getData = async () => {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: GET_ID,
variables: {
targetUrl: req.originalUrl,
},
}),
};
const response = await fetch(process.env.ENDPOINT, options);
const json = response.json();
return json;
};
try {
const result = await getData();
res.redirect(`/my-page/?id=${result.data.page.content.id}`);
} catch (err) {
console.error(err);
}
}
};
export default videoRedirectHandler;
Then I call it in the server file like:
videoRedirectHandler(req, res)
But now I get this error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
In the not-extracted example (initial code), I fixed this issue by adding return
to the end of the if-block. But in my videoRedirectHandler
function it's not possible.
How to fix this?
Update: Thanks to @bergi I created two separate functions, which are working fine:
// getPageData()
import GET_PAGE_DATA from './getPageData.gql';
const getPageData = async (targetUrl: string) => {
const getData = async () => {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: GET_PAGE_DATA,
variables: {
targetUrl,
},
}),
};
const response = await fetch(process.env.ENDPOINT, options);
const json = response.json();
return json;
};
const result = await getData();
if (result.errors?.length) {
throw new Error('Unable to parse json response');
}
return result.data.page;
};
export default getPageData;
Inside above function I created another function getData()
. Is that necessary?
// getRedirectUrl()
import getPageData from './getPageData';
const getRedirectUrl = async (url: string, originalUrl: string) => {
if (url.includes('/foo')) {
const { content } = await getPageData(originalUrl);
if (content.type === 'MyPage') {
return `/my-page/?id=${content.id}`;
}
}
return null;
};
export default getRedirectUrl;
Why did you used url
and originalUrl
here?
Update 2:
In the browser when requesting for a non-existing url (but which contains /foo
in the url), it gives a blank screen now. In terminal it throws new Error('Something went wrong');
from getPageData()
function.
When I inspect result.errors
it gives:
[
{
message: '404: Not Found',
extensions: {
code: 'INTERNAL_SERVER_ERROR',
serviceName: 'node',
response: [Object],
exception: [Object]
}
}
]
And in GraphQL playground it gives:
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"serviceName": "node",
"response": {
"url": "http://fooo/path/etc",
"status": 404,
"statusText": "Not Found",
"body": {
"message": "No route found for \"GET /fooo/path/etc\""
}
},
I think because the GraphQL query works only if the targetUrl
is correct. In GraphQL playground it gives the same error when adding a non existing url.
How to fix this issue? It should return to 404 page or something like that and get out of this function. Below both functions:
getPageData():
const getPageData = async (targetUrl: string) => {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: GET_DATA,
variables: {
targetUrl,
},
}),
};
const response = await fetch(process.env.ENDPOINT, options);
if (!response.ok) {
throw new Error('Unable to parse json response');
}
const result = await response.json();
if (result.errors?.length) {
console.log('foo', result.errors);
throw new Error('Something went wrong');
}
return result.data.page;
};
export default getPageData;
And getRedirectUrl():
import getPageData from './getPageData';
const getRedirectUrl = async (url: string) => {
if (url.includes('/foo')) {
const { content } = await getPageData(url);
if (content.type === 'MyPage') {
return `/my-page/?id=${content.id}`;
}
}
return null;
};
export default getRedirectUrl;
Update 3:
When I add return { content: { id: null, type: null } };
inside if statement:
if (result.errors?.length) {
// throw new Error('Something went wrong');
return { content: { id: null, type: null } };
}
It works fine. Is there a better/cleaner way to solve this issue?
Update 4:
I fixed the issue by adding try catch
inside getRedirectUrl()
function:
const getRedirectUrl = async (url: string) => {
if (requestUrl.includes('/foo')) {
try {
const { content } = await getData(url);
if (content.type === 'MyPage') {
return `/my-page/?id=${content.id}`;
}
} catch (error) {
console.error(error);
}
}
return null;
};
Do not extract the if (req.url.includes('foo')) {
part, and particularly the early return
, into the function as well. Keep that in your original code:
if (req.url.includes('foo')) {
videoRedirectHandler(req, res);
return;
}
The return
keyword is crucial. It prevents the rest of your router handler function from running. You cannot move that into a different function and expect the control flow to be the same.
Alternatively, you can write a helper function that determines the redirect URL conditionally, and have your routing code use its result:
async function getPageData(targetUrl) {
…
if (result.errors?.length) throw new Error(…);
return result.data.page;
}
async function getRedirectUrl(url, originalUrl) {
if (req.url.includes('foo')) {
const {content} = await getPageData(originalUrl);
if (content.type === 'Bar') {
return `/my-page/?id=${content.id}`;
}
}
}
These are two functions you can easily unit-test. Then in the server's request handler, write
const redirectUrl = getRedirectUrl(req.url, req.originalUrl);
if (redirectUrl) {
res.redirect(redirectUrl);
return;
}
…