I have generated my API client with openapi-generator-cli generate -i https://linktomybackendswagger/swagger.json -g typescript-axios -o src/components/api --additional-properties=supportsES6=true
Now I have all the files inside my project but I have no clue how to implement this.
How do I instantiate the API? Where do I configure the access token to be used? How do I know each method name for an endpoint?
After 2 hours of googling I can't seem to find a documentation for what seems like the most basic setup questions. Maybe I'm just looking in the wrong places. Can someone point me in the right direction?
Ok, so I figured out a way that I think is clean that I will document here for others that are going down the same path, which is:
Using an API that is using Authorization: Bearer <Token here>
Created the client with openapi-generator-cli using -g typescript-axios
Using OAS3
Let's say you have an endpoint called UserPolicies. After generating the code via CLI each endpoint will have its own class inside the generated file api.ts with the name extended like so UserPoliciesApi.
For using that endpoint the following setup is required.
Example: Inside UserPolicyList.tsx:
import { UserPoliciesApi } from './components/api/api';
import { Configuration } from './components/api/configuration';
const openapiConfig = new Configuration();
openapiConfig.baseOptions = {
headers: { Authorization: 'Bearer ' + cookies.access_token },
};
const openapi = new UserPoliciesApi(openapiConfig);
Let's assume you want to make a GET call for api/Policies you can do so with:
openapi.userPoliciesGetPolicies.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
Now, what I found inconvenient that with this design is the boilerplate code necessary for making a simple api call. I wanted to be able to simply do one import and already have the access_token setup.
So I created a wrapper class like this MyApi.tsx:
import { Cookies } from 'react-cookie';
import { Configuration } from './components/api/configuration';
class MyApi {
private cookies: Cookies;
constructor() {
this.cookies = new Cookies();
}
private accessToken = () => {
return this.cookies.get('access_token');
};
private configuration = () => {
const openapiConfig = new Configuration();
openapiConfig.baseOptions = {
headers: { Authorization: 'Bearer ' + this.accessToken() },
};
return openapiConfig;
};
public userPoliciesApi = () => {
const api = new UserPoliciesApi(this.configuration());
return api;
};
}
export default MyApi.tsx;
Now I you can easily replace the boilerplate and call with this:
Inside UserPolicyList.tsx:
import { MyApi } from './components/myapi/MyApi.tsx';
const api = new MyApi();
api.userPoliciesApi.userPoliciesGetPolicies.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
Related
I have an SPA (reactjs) site that has some link like http://example.com/blog/:id
and I want to dynamically create a dynamic sitemap.
I know that an npm package like react-router-sitemap can generate a sitemap with a command but I want to create sitemap.xml every time that this URL gets visited: http://example.com/sitemap.xml. That way if I create a new blog post, I don't need to re-create the sitemap again.
How should I do this?
You can run a cron-job to achieve this. Load the blog ids you need and write to the sitemap.xml in the build folder.
Try like below. You need few packages installed; node-cron, sitemap and axios.
I have set up a simple express server to server-rendering react UIs and I call startSiteMapGeneratorJob function from there. This is working fine for me.
SiteMapGenerator.js
import cron from "node-cron";
import { SitemapStream, streamToPromise } from "sitemap";
import { Readable } from "stream";
import fs from "fs";
import axios from "axios";
import { BACKEND_API_URL } from "../constants/APIConstants";
const startSiteMapGeneratorJob = () => {
generateBlogSitemap();
console.log("site map job started");
cron.schedule("* * 12 * *", function() {
console.log("Running a task every 12 hours (twice a day)");
generateBlogSitemap();
});
};
export const generateBlogSitemap = async () => {
try{
const res = await axios.get(`${BACKEND_API_URL}/blog/getAllIDs`);
const { success, blogIDs = [] } = res.data;
if (success && blogIDs.length > 0) {
// An array with the links
const links = blogIDs.map(({ _id }) => {
return { url: `/blog/${_id}` };
});
// Create a stream to write to
const stream = new SitemapStream({ hostname: "https://example.com" });
// Return a promise that resolves with your XML string
const data = await streamToPromise(Readable.from(links).pipe(stream));
fs.writeFileSync("./build/sitemap.xml", data.toString());
console.log("Blog Sitemap Generated");
} else {
console.log("No Blog Sitemap Generated");
}
} catch(error){
console.log("Sitemap generator error", error)
}
};
export default startSiteMapGeneratorJob;
I am trying to create an Express Router endpoint that will return the CSV file from an external API (Jenkins in this case)
In more detail, what I am trying to achieve is to have a React Frontend call this route on the Express backend and download a CSV file.
BACKEND
The Express route is has this structure:
router.get('/path/latestCsvTestReport', async (req, res) => {
const { channel } = req.params;
return fetch(
`${jenkinsHost}/job/${channel}/${jenkinsPath}/lastSuccessfulBuild/artifact/test_result/report_test.csv`, {
...fetchOptions,
headers: { Authorization: jenkinsAuth},
},
)
.then(r => {
console.log('====== DATA =====', r);
res.setHeader('Content-type', 'text/csv');
res.setHeader('Cache-Control', 'no-cache');
res.send(r)
})
.catch((err) => {
// console.log(err);
res.status(404);
res.send('report not found');
});
});
and the URL called in the fetch returns a CSV file.
FRONTEND
I am calling the Express endpoint from a method on the React frontend using the following function, which utilised the file-saver library:
async function triggerReportDownload(chlId) {
console.log('===== CSV Request ====')
const resource = `/api/jenkins/${chlId}/latestCsvTestReport`;
saveAs(resource, "report.csv")
}
which is triggered by the click of a button on the FrontEnd.
At the moment, the button, triggers a download but the csv downloaded only contains:
{"size":0 timeout:0}
I am certain I am doing something completely wrong on the way the backend returns the CSV from the fetch call, but for the life of me I do not seem to be able to find the way to formulate the response. Any help/direction towards fixing this would be greatly appreciated.
The solution to this is to simply things as possible (being a newbie I had overcomplicated things). So here we are:
Backend
Import the utils library and then create a stream:
import util from 'util';
const streamPipeline = util.promisify(require('stream').pipeline);
This is then called from the Express router:
router.get('/jenkins/:channel/latestCsvTestReport.csv', async (req, res) => {
const { channel } = req.params;
const response = await fetch(
`${jenkinsHost}/job/${channel}/${jenkinsPath}/lastSuccessfulBuild/artifact/test_result/report_test.csv`, {
...fetchOptions,
headers: { Authorization: jenkinsAuth },
},
);
res.setHeader('Content-disposition', `attachment; filename=report_test_${Date.now()}.csv`);
res.set('Content-Type', 'text/csv');
return streamPipeline(response.body, res);
});
Frontend
Use windows.open to get the download file
async function triggerReportDownload(chlId) {
window.open(`/api/jenkins/${chlId}/latestCsvTestReport.csv`);
}
Looking for an opinion on best practice going forward. We are using React with Typescript and starting a new project. Looking at previous projects I am seeing 2 different styles of programming along with most examples in here, on blogs and in documentation. I am wondering if there is a performance/memory or any difference between the 2 sample codes
Sample 1
export class CustomerService extends BaseService {
public async GetBase(contactId: number): Promise<Contact> {
try {
let res = await fetch(this.BASEURL + 'Contacts/' + contactId.toString(),
{
method: 'GET',
headers: {
'Authorization': `Bearer ${this._authentication.token.access_token}`
}
});
let results = await this.getBody(res);
this.checkForErrors(res, results);
let errors: ErrorList = this.checkForValidationErrors(res, results);
if (errors != null) {
return Promise.reject(errors);
}
return this._getContactEntity(results);
}
catch (err) {
let errorList: ErrorList = this.buildErrorList(err, { 'key': 'Get Error', 'value': 'Failed to get contact', level: 0 });
throw errorList;
}
}
}
//Sample 2
export const getSurvey = async (surveyPageId: number): Promise<Survey> => {
const response = await getRequest('Survey', { contactId });
const result = await response.json();
const survey = new Survey(result[0]);
return survey;
}
export const getRequest = async (endpoint: string, params?: URLParamType): Promise<Response> => {
const endpointUrl = new URL(endpoint, document.location.href);
if (params) {
Object.keys(params).forEach(k => {
endpointUrl.searchParams.append(k, params[k].toString());
})
}
return fetcher(endpointUrl.toString(), {
method: 'GET'
})
}
I see some things that are different error handling and authentication in sample 1 but that could be fitted into sample 2 as well. Wondering how I should proceed on the new project.
It looks like you're asking whether you should be using class components or functional components with hooks, in which case the answer is definitely the latter. Though class components are perfectly valid and will continue to be supported for a while, they are slowly being phased out in favor of functional components/hooks, so if you're starting a project from scratch there's no reason not to use them.
I'd recommend reading the documentation on hooks for a more in-depth explanation of why they were introduced and what advantages they offer over class-based components. Everything that can be accomplished with class components can be done with hooks (except error boundaries I believe), though the implementation will certainly be different.
I'm getting a TypeError from webpack when using a function from an imported class.
I get TypeError: __WEBPACK_IMPORTED_MODULE_6_clients_FilesClient__.a.upload(...) is undefined
I'm exporting my class with:
export default(FilesClient = new FilesClient());
and importing it into my component with:
import FilesClient from "clients/FilesClient";
and when I use the client like this:
acceptedFiles.forEach(file => {
FilesClient.upload(file, this.props.proposalId)
.then(data => {
....
the type error is thrown. I'm not sure why this is... I'm able to log the object as well as the function to the console and the request is made successfully but, the react app crashes before the response comes back as ok.
This is the same way that I've exported/imported several other clients, however I've only used those to make GET requests so far... Not sure why this one is messed up. I can take the function out of this client class and it's fine, just want to figure out the problem with Webpack.
Just for reference here's the class with the function that Webpack says is undefined:
import Client from "./Client";
class FilesClient extends Client {
upload = (file, proposalId) => {
const url = `/api/proposals/${proposalId}/uploadfile`;
let formData = new FormData();
formData.append("file", file, file.name);
fetch(url, {
method: "POST",
body: formData
}).then(response => {
if (!response.ok) {
console.log(response);
throw Error("File upload failed", response);
}
return response.json();
});
}
}
export default (FilesClient = new FilesClient());
Thanks in advance!
export default new FilesClient();
Try this it should give you desired result, What you are doing now is wrong as it is an assignment and it returns nothing.
Here is codesandbox link
example
I´m using JWT authentication inside my ReactJS RelayJS network environment. All the token retrieval and processing in server and client are fine. I´m using react router v4 for routing.
My problem is when I receive a Unauthorized message from server (status code 401). This happens if the user points to an application page after the token has expired, ie. What I need to do is to redirect to login page. This is the code I wish I could have:
import { Environment, Network, RecordSource, Store } from 'relay-runtime';
const SERVER = 'http://localhost:3000/graphql';
const source = new RecordSource();
const store = new Store(source);
function fetchQuery(operation, variables, cacheConfig, uploadables) {
const token = localStorage.getItem('jwtToken');
return fetch(SERVER, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + token,
Accept: 'application/json',
'Content-type': 'application/json'
},
body: JSON.stringify({
query: operation.text, // GraphQL text from input
variables
})
})
.then(response => {
// If not authorized, then move to default route
if (response.status === 401)
this.props.history.push('/login') <<=== THIS IS NOT POSSIBLE AS THERE IS NO this.history.push CONTEXT AT THIS POINT
else return response.json();
})
.catch(error => {
throw new Error(
'(environment): Error while fetching server data. Error: ' + error
);
});
}
const network = Network.create(fetchQuery);
const handlerProvider = null;
const environment = new Environment({
handlerProvider, // Can omit.
network,
store
});
export default environment;
Naturally calling this.props.history.push is not possible as the network environment is not a ReactJS component and therefore has no properties associated.
I´ve tried to throw an error at this point, like:
if (response.status === 401)
throw new Error('Unauthorized');
but I saw the error on the browser console, and this cannot be treated properly in the code.
All I wanna do is to redirect to login page in case of 401 error received, but I can´t find a proper way of doing it.
I am not using relay but a render prop. I experienced kind of the same issue. I was able to solve it using the window object.
if (response.statusText === "Unauthorized") {
window.location = `${window.location.protocol}//${window.location.host}/login`;
} else {
return response.json();
}
You can go with useEnvironment custom hook.
export const useEnvironment = () => {
const history = useHistory(); // <--- Any hook using context works here
const fetchQuery = (operation, variables) => {
return fetch(".../graphql", {...})
.then(response => {
//...
// history.push('/login');
//...
})
.catch(...);
};
return new Environment({
network: Network.create(fetchQuery),
store: new Store(new RecordSource())
});
};
// ... later in the code
const environment = useEnvironment();
Or you can create HOC or render-prop component if you are using class-components.
btw: this way you can also avoid usage of the localStorage which is slowing down performance.