In my sailsJS project i use a Service as an API wrapper to communicate with an external API.
For example I want to have a init function and pass parameters like baseUrl, apiKey and after that just call sendRequest(endpoint).
However today i figured out that a service cannot be instantiated but is available globally. Because SailsJS works async it just overrides the variables each time.
So my question in short: What's the right way to use a "class" with class variables, where i can have multiple instances, like i know them from the PHP world?
At first i want to instantiate my API wrapper with a project, that contains basic data such as baseUrl and apiKey.
const ApiService = ServiceBase.getApiService(project);
ApiService.getEndpoint(endpoint).then(async(response) => {
// handle response
}
Function get ApiService.
Here i try to set project variables.
getApiService: (project) => {
this.setConfigParam('apiKey', project.api_key);
this.setConfigParam('baseUrl', project.url);
return ShopApiService;
}
Function getEndpoint (Shortcut for GET Requests)
getEndpoint: async function(endpoint) {
if (ShopApiService.isTokenExpired()) {
await this.authorizeRequest();
}
return this.sendRequest(endpoint);
}
authorizeRequest (Sets accessToken for following requests)
authorizeRequest: async function() {
let response = await this.postRequest('auth/login', {apiKey: module.exports.config.apiKey});
if (response && response.statusCode == 200) {
module.exports.token.updated_at = Date.now();
module.exports.config.accessToken = response.body.accessToken;
return true;
}
return false;
}
My problem is that the asynchronous calls override variables like accessToken or baseUrl, so if the request to acquire the access token is finished - it may be for a different request.
Related
I am building a React/Typescript app, running completely client-side in browser. In componentDidMount(), I make a fetch request which I intercept successfully, to change the URL, and then make that request.
For reference, the API object is also from a third party library, loaded in via an HTML script tag, so I don't have access to the inner workings of the object. That's why I'm attempting to intercept the call instead to point the URL at a different endpoint.
async function makeRequest() {
let originalFetch = redefineFetch();
let data;
try {
data = await API.fetchData();
} catch (error) {
resetFetch(originalFetch);
return;
}
resetFetch(originalFetch);
return data;
}
const redefineFetch = () => {
const { fetch: originalFetch } = window;
let originalWindowFetch = window.fetch;
window.fetch = async (...args) => {
let [resource, config] = args;
resource = NEW_URL;
const response = await originalFetch(resource, config);
return response;
};
return originalWindowFetch;
};
const resetFetch = (
originalFetch: ((input: RequestInfo | URL, init?: RequestInit | undefined) => Promise<Response>) &
((input: RequestInfo | URL, init?: RequestInit | undefined) => Promise<Response>)
) => {
console.log("Resetting fetch");
window.fetch = originalFetch;
};
How I'm currently doing it:
I copied how it was done in this blog post. https://blog.logrocket.com/intercepting-javascript-fetch-api-requests-responses/.
As you can see, makeRequest() calls redefineFetch(), which redefines window.fetch to point to the NEW_URL instead.
redefineFetch() returns the original implementation of fetch as originalFetch.
After making the request, I call resetFetch() and pass originalFetch.
I then set window.fetch = originalFetch.
What I think is the issue
Every request including and after API.fetchData() now point to the NEW_URL.
These requests are out of my control in timing as they are made by 3rd party portions of my code.
I think I'm either not setting window.fetch back to its original value correctly, OR there's a race condition in which these mistakenly intercepted requests are being made before resetFetch() is called.
My Questions
How can I redefine fetch only for the API.fetchData() call without risking affecting any other calls made in my app?
Is there a better way to accomplish what I'm doing?
I'm new to Next.js and I'm trying to understand the suggested structure and dealing with data between pages or components.
For instance, inside my page home.js, I fetch an internal API called /api/user.js which returns some user data from MongoDB. I am doing this by using fetch() to call the API route from within getServerSideProps(), which passes various props to the page after some calculations.
From my understanding, this is good for SEO, since props get fetched/modified server-side and the page gets them ready to render. But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps(). So what am I suppose to do to comply to good practice and good SEO?
The reason I'm not doing the required calculations for home.js in the API route itself is that I need more generic data from this API route, as I will use it in other pages as well.
I also have to consider caching, which client-side is very straightforward using SWR to fetch an internal API, but server-side I'm not yet sure how to achieve it.
home.js:
export default function Page({ prop1, prop2, prop3 }) {
// render etc.
}
export async function getServerSideProps(context) {
const session = await getSession(context)
let data = null
var aArray = [], bArray = [], cArray = []
const { db } = await connectToDatabase()
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
if (session) {
const hostname = process.env.NEXT_PUBLIC_SITE_URL
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/user`, options)
const json = await res.json()
if (json.data) { data = json.data }
// do some math with data ...
// connect to MongoDB and do some comparisons, etc.
But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().
You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps. Note that this only applies to calls to internal API routes - it's perfectly fine to call external APIs from getServerSideProps.
From Next.js getServerSideProps documentation:
It can be tempting to reach for an API Route when you want to fetch
data from the server, then call that API route from
getServerSideProps. This is an unnecessary and inefficient approach,
as it will cause an extra request to be made due to both
getServerSideProps and API Routes running on the server.
(...) Instead, directly import the logic used inside your API Route
into getServerSideProps. This could mean calling a CMS, database, or
other API directly from inside getServerSideProps.
(Note that the same applies when using getStaticProps/getStaticPaths methods)
Here's a small refactor example that allows you to have logic from an API route reused in getServerSideProps.
Let's assume you have this simple API route.
// pages/api/user
export default async function handler(req, res) {
// Using a fetch here but could be any async operation to an external source
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
res.status(200).json(jsonData)
}
You can extract the fetching logic to a separate function (can still keep it in api/user if you want), which is still usable in the API route.
// pages/api/user
export async function getData() {
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
return jsonData
}
export default async function handler(req, res) {
const jsonData = await getData()
res.status(200).json(jsonData)
}
But also allows you to re-use the getData function in getServerSideProps.
// pages/home
import { getData } from './api/user'
//...
export async function getServerSideProps(context) {
const jsonData = await getData()
//...
}
You want to use the logic that's in your API route directly in
getServerSideProps, rather than calling your internal API. That's
because getServerSideProps runs on the server just like the API routes
(making a request from the server to the server itself would be
pointless). You can read from the filesystem or access a database
directly from getServerSideProps
As I admit, what you say is correct but problem still exist. Assume you have your backend written and your api's are secured so fetching out logic from a secured and written backend seems to be annoying and wasting time and energy. Another disadvantage is that by fetching out logic from backend you must rewrite your own code to handle errors and authenticate user's and validate user request's that exist in your written backend. I wonder if it's possible to call api's within nextjs without fetching out logic from middlewars? The answer is positive here is my solution:
npm i node-mocks-http
import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
return (
<section>
<h2>Latest News</h2>
<NewsCard news={news} />
</section>
);
}
export async function getServerSideProps() {
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
async function callMids(req, res, index, ...mids) {
index = index || 0;
if (index <= mids.length - 1)
await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
}
await callMids(
req,
res,
null,
dbConnectMid,
logger,
newsController.sendAllNews
);
return {
props: { news: res._getJSONData() },
};
}
important NOTE: don't forget to use await next() instead of next() if you use my code in all of your middlewares or else you get an error.
Another solution: next connect has run method that do something like mycode but personally I had some problems with it; here is its link:
next connet run method to call next api's in serverSideProps
Just try to use useSWR, example below
import useSWR from 'swr'
import React from 'react';
//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());
const Categories = () => {
//getting data and error
const { data, error } = useSWR('/api/category/getCategories', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
if (data){
// {data} is completed, it's ok!
//your code here to make something with {data}
return (
<div>
//something here, example {data.name}
</div>
)
}
}
export default Categories
Please notice, fetch only supports absolute URLs, it's why I don't like to use it.
P.S. According to the docs, you can even use useSWR with SSR.
I'm currently writing a small program in Node.js for an express server. I am using providers to facilitate the separation of concerns. What I'm having trouble with is figuring out how to send a return value from the provider function back to the express server and then to the client. I'm probably missing something with the asynchronous code but can't seem to figure out what. I'd be grateful if you could guide me in the right direction! Sorry if this is an obvious question, I'm very new to programming.
This is the code skeleton for the main.js:
server.get("/someurl*", (req, res) => {
let name = req.query.name;
let id = req.query.id;
if (
(isValid(name) == true) &&
(isValid(id) == true)
) {
let provider = new getSomething();
provider.getMethod(id);
res.send(provider.getMethod(id));
};
});
This is the code skeleton for provider.js:
class getSomething extends abstractClass {
getMethod(id) {
this.id = id;
// Acquiring data from database based on id
…
if (err)
return (‘error’);
return data;
};
};
What I do not understand is how can I send the data back to the express server (main.js) and then send that data back to the client with res.send. The way I'm doing it above doesn't work as it returns an undefined value. I cannot do res.send directly from the provider as it should only be responsible for retrieving data from the database and the server instance is defined in main.js.
Thank you very much in advance!
In your provider class I would ammend the getMethod to be something more like this:
getMethod(id) {
this.id = id;
//do something. update this.data
if (err) this.error = err;
//optional for method chaining: return this;
}
Class methods can be used to modify the properties of the class, which you can then send to the client.
then in your endpoint:
server.get(“/someurl*”, (req, res) => {
const name = req.query.name; //use consts for values that you dont want to change
const id = req.query.id;
if ((isValid(name) == true) && (isValid(id) == true)) {
const provider = new getSomething();
provider.getMethod(id);
if (provider.error) return res.status(500).json({error: provider.error});
return res.status(200).json({data: provider.data});
};
return res.status(500).error({error: 'query data not valid'});
});
this is extracting the data you require from the query object, then if everything is valid (im asuming some other function you have for validation) will instantiate a new instance of your class, run the method, and if theres an error send this back to the client otherwise send back data to the client.
If the data is not valid it also sends back a response to the client. In any case you want to send something back to the client.
I'm trying to implement a service for my backend that allows the session data to be called from anywhere in the code, which means I want to create a service file that exports the values from the functions that get the session data. Otherwise I can only get the session data from inside functions that have both req: Request and res: Response parameters. So I'm basically trying to lock the values to a variable that can be used and called from anywhere in my project. My code now looks like this but if I use the exports anywhere else in the file, it just prints the actual code (function) snippet instead of the return value specified inside the code. I'm pretty new to typescript and node in general which means I might be doing some really silly errors here.
Thanks for all the help in advance!
/Victor
import { Request, Response, NextFunction } from "express";
function getUserSessionData(req: Request, res: Response) {
const userData = res.status(200).send(req.session.userData);
return userData;
}
function getUserSessionLang(req: Request, res: Response) {
const userLang = res.status(200).send(req.session.language);
return userLang;
}
function getUserSessionAll(req: Request, res: Response) {
const userAll = res.status(200).send(req.session);
return userAll;
}
module.exports = {
userData: getUserSessionData,
userLang: getUserSessionLang,
userAll: getUserSessionAll
};
How I would like it to work:
const sessionService = require("./services/sessionService");
function getStuff(req: Request, res: Response, next: NextFunction) {
let redisKey;
if (!req.session.language) {
redisKey = "getStuffEN";
}
else {
redisKey = "getStuff" + sessionService.userLang;
}
console.log(redisKey);
getRedis(redisKey, function success(reply: any) {
res.status(200).json(
JSON.parse(reply)
);
}, function error(err: Error) {
console.log("Something went wrong");
});
}
This is how it is right now (and working)
function getStuff(req: Request, res: Response, next: NextFunction) {
let redisKey;
if (!req.session.language) {
redisKey = "getStuffEN";
}
else {
redisKey = "getStuff" + req.session.language;
}
console.log(redisKey);
getRedis(redisKey, function success(reply: any) {
res.status(200).json(
JSON.parse(reply)
);
}, function error(err: Error) {
console.log("Something went wrong");
});
}
I want it to work like the first example, since there are some instances in my code where I want to access the data without having to pass the req, res parameters, if possible.
First, a short explanation on sessions:
When a user logs in (and you verify his credentials, etz) you start a new session for that user.
The middleware you're using will assign this session a unique ID and you can assign some data to it.
The ID is transferred to the users Browser in form of a cookie
The Data is stored on the Server (in Redis for your case)
The middleware will check if a session-cookie with a valid ID is included in a request and do the following:
Fetch Session-Data for the given ID from Redis
Populate the req.session-object with the Data from Redis
Call the next Route
With this out of the way, a word of advice: Don't store the session-data in your applications memory. Why? The data should only be relevant in the context of a request from a user. You'll need the session data to handle the request, but you don't need it otherwise.
Instead of storing it globally, build your handlers and functions to accept the specific Session-Data as parameters, like this:
// handler, after middleware:
const getUserBio = (req, res) => {
const userId = req.session.userId;
const bioData = fetchBio(userId);
res.render("userBio", bioData);
}
// somewhere else in your code
function fetchBio(userId) {
const fullBio = database.fetchBio(userId);
return {
full: fullBio,
excerpt: fullBio.substring(0, 24);
}
}
This has two important advantages:
You don't have to keep the session-Data in your memory synchronized with the one in Redis
These (almost) pure functions make your code easier to understand
If you write functions that work entirely on their input parameters and don't use any global state, things like "order in which to call functions" or "check if data is available" become irrelevant. The caller is responsible for getting the data, the callee is responsible for working with it.
Extending on that, express routes should never use any global in-memory data, if you ever want to scale your application horizontally (by using multiple instances). It can't be guaranteed that the same client will always connect to the same server-instance, so the globally stored data might be available for one request but not for the next. In this case, you'll have to find a way to share the global data between all your instances, which is what Redis already does in your case.
tl;dr: Only access the session-data in a request-handler, then pass it on as parameters to any functions that need to work on it. Don't keep global in-memory state in your server if you ever want to scale it horizontally.
You can write data to a db or to a file as it say madvic, but we can use also global variables. But global variables is a bad practise, as I know, so It's better to write someone your data.
I have JSON API built with koa which I am trying to cover with integration tests.
A simple test would look like this:
describe("GET: /users", function() {
it ("should respond", function (done) {
request(server)
.get('/api/users')
.expect(200, done);
});
});
Now the issue comes when the actions behind a controller - lets say saveUser at POST /users - use external resources. For instance I need to validate the users phone number.
My controller looks like this:
save: async function(ctx, next) {
const userFromRequest = await parse(ctx);
try {
// validate data
await ctx.repo.validate(userFromRequest);
// validate mobile code
await ctx.repo.validateSMSCode(
userFromRequest.mobile_number_verification_token,
userFromRequest.mobile_number.prefix + userFromRequest.mobile_number.number
);
const user = await ctx.repo.create(userFromRequest);
return ctx.data(201, { user });
} catch (e) {
return ctx.error(422, e.message, e.meta);
}
}
I was hoping to be able to mock the ctx.repo on the request object but I can't seem to able to get a hold on it from test, which means that my tests are actually hitting the phone number verification service.
Are there any ways I could go around hitting that verification service ?
Have you considered using a mockup library like https://github.com/mfncooper/mockery?
Typically, when writing tests requiring external services, I mock the service client library module. For example, using mocha:
mockery = require('mockery');
repo = require('your-repo-module');
before(function() {
mockery.enable();
repo.validateSMSCode = function() {...};
mockery.registerMock('your-repo-module', repo);
}
This way, every time you require your-repo-module, the mocked module will be loaded rather than the original one. Until you disable the mock, obviously...
app.context is the prototype from which ctx is created from. You may
add additional properties to ctx by editing app.context. This is
useful for adding properties or methods to ctx to be used across your
entire app, which may be more performant (no middleware) and/or easier
(fewer require()s) at the expense of relying more on ctx, which could
be considered an anti-pattern.
app.context.someProp = "Some Value";
app.use(async (ctx) => {
console.log(ctx.someProp);
});
For your sample your re-define app.context.repo.validateSMSCode like this, assuming that you have following setup lines in your test:
import app from '../app'
import supertest from 'supertest'
app.context.repo.validateSMSCode = async function(ctx, next) {
// Your logic here.
};
const request = supertest.agent(app.listen())
After re-defining app.context.repo.validateSMSCode method that your will define in your test, will work, instead of original method.
https://github.com/koajs/koa/blob/v2.x/docs/api/index.md#appcontext
https://github.com/koajs/koa/issues/652