Asynchronous Axios Initiation - javascript

I am trying to initiate an Axios Client for my React app. I want to use the authenticated client to make requests. The current setup looks as followed. Due to the session service being async to check for a cached token and validating it against the server the initiation of the API Client is also async. This results in the client in the ExampleService being undefined when constructed too early.
The goal is to keep the Services as self contained as possible.
class ApiClientService {
client: AxiosInstance
async init() { // gets called asap on launch
await sessionService.init()
this.client = axios.create({
baseURL: config.host,
timeout: 1000,
headers: { Authorization: `token ${sessionService.getToken()}` }
})
}
getClient() {
return this.client
}
...
}
class ExampleService {
private client = apiService.getClient()
async doSomething() {
return this.client.get('/something')
}
}

Related

Express doesn't set cookies with httpOnly flag

I have my back-end Express.js server that has sign in function. After user sign in, he gets 2 tokens - access token and refresh token. What I want to do, is to make return from server refresh token as httpOnly cookie.
Here is a peace of code of this function:
const { refreshToken, accessToken } = await jwtService.updateTokens({
userId: client.id, username: client.username
}, { transaction })
logger.info(`Client ${client.email} has been successfully signed in!`)
await transaction.commit()
return res
.status(200)
.cookie("refreshToken", JSON.stringify(refreshToken), { httpOnly: true, secure: false })
.json({ accessToken, reopening: reopening ? client.username : null })
Basically, browser just doesn't set this cookie as httpOnly and doesn't set it at all, actually. So, I was trying to ping this endpoint with postman, and it works:
In reponse body I have access token and in httpOnly cookie I have this refresh token.
So, the problem is, why browser doesn't save it? I have found a couple of solutions and all of them were about cors and axios credentials. I use 2 express servers - 1 is for normal back-end and 1 is for front-end.
Here is how "path" from front-end to back-end looks like:
Sign in function that send request to front-end express server:
const api = axios.create({
baseURL: apiUrl,
headers: {
'Content-Type': 'application/json'
}
})
export const signIn = async payload => {
try {
const { data } = await api.post('s-i', payload)
return data
} catch (e) {
return e.response.data
}
}
Front-end express server sends request to actual back-end:
const api = axios.create({
baseURL: process.env.NODE_ENV === "development" ? process.env.PNB_API_DEV : process.env.PNB_API_PROD,
})
const router = Router()
router.post('/s-i', async (req, res) => {
try {
const { data } = await api.post('/sign-in', req.body)
res.json(data)
} catch (e) {
return res.status(e.response.status).json(e.response.data)
}
});
And then that function that was at the very begging.
So - the question is - how to make browser save those httpOnly cookies? If it's really about credentials or cors where should I put those settings?
PS
Back-end port - 3001 and front-end port - 8010.

React 404 Axios Cannot POST

New to Axios and Node as a backend- have inherited some code in React for a login page and am trying to figure out why I can't make a POST to the backend.
This is my .env file:
REACT_APP_BACKEND_URL=http://localhost:3000/admin
And there is a file called API.js
import axios from "axios";
// Set config defaults when creating the instance
export const CompanyAPI = () => {
let api = axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL,
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
});
return api;
};
And then there is a LoginPage.js:
export const LoginPage = () => {
const API = CompanyAPI();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const getAuth = (e) => {
e.preventDefault();
API.post("/auth/login", {
email: email,
password: password,
})
.then(async (response) => {
if (response.status === 200) {
await localStorage.setItem("jwt_key_admin", response.data.token);
setTokenKey(response.data.token);
setIsAuth(true);
navigate("/");
}
})
.catch((error) =>
alert(
"Login Failed"
)
);
};
My question is, is there an example on how I could use express to handle the /auth/login endpoint and complement the existing API.js file?
Basically what I see from this code is:
An axios instance was created and the baseURL was set to being http://localhost:3000/admin
From first glance I can tell that all Api calls that you make a resulting to 404 reason being React locally will always run on port 3000 unless that port is in use.
So now your axios baseURL being to set to port 3000 definitely axios should return a 404 because you surely do not have the endpoint that you are trying to hit
Solution:
Here you are to change the baseURL's port number to the port where Nodejs server is listening on
Then once that is said and done then make sure that even the endpoints that you are trying to hit do exist then your axios calls should work now
Advise:
If the Axios instance created is confusing drop it for a bit and import raw axios not the instance then use axios in it's basic form then once you have that working you surely will have established the correct port number and everything then you can edit the axios instance created with baseURL you have established.
Add
"proxy":"http://localhost:3000/admin"
in your package.json file and restart your React App.

How to call nestjs microservice from any frontend app?

I have a made a NestJS microservice package and separate NestJS client application for contacting the microservice. Below given is the code used in the client application. In microservice the method used is #messagePattern and it is functional. My question is how a front-end app can contact directly to the microservice without going through the client also how to setup swagger in microservice or test it directly from a postman ?
import { BadRequestException, Injectable, UnauthorizedException } from '#nestjs/common';
import { ClientProxy, ClientProxyFactory, Transport } from '#nestjs/microservices';
import { errorResponse, goodResponse } from 'src/helpers/response.helper';
import { AddContractDTO } from './contract.dto';
#Injectable()
export class ContractService {
private client: ClientProxy;
constructor() {
this.client = ClientProxyFactory.create({
transport: Transport.TCP,
options: {
host: '127.0.0.1',
port: 3011,
},
});
}
public async addContract(data: AddContractDTO) {
const res = await this.client.send<any,any>('contract/addContract', data).toPromise();
console.log(res);
if(!res.success){
throw new BadRequestException(res)
}
return goodResponse(res.data.data,'Contract created');
}
}
You cannot call the service directly. You need to create a controller (to bind to an endpoint) which then can call the service.
Examples can be found in the NestJS Documentation (https://docs.nestjs.com/microservices/basics).

Pass object to Express export function

I'm working (and learning) on my TypeScript skills, although I ran into a problem: I have a class named Manager which contains and manages multiple 'sub' managers. In the index file, I load the Manager by creating an instance and calling the load function. When loading all 'sub' managers get a reference to the main/only Manager instance, this way they can call/use the other 'sub' managers.
But I would like to be able to get some info from the 'sub' managers at a REST API endpoint. These endpoints are loaded through routes:
index.ts
import "reflect-metadata";
import { createConnection } from "typeorm";
import { Request, Response } from "express";
import * as express from "express";
import * as bodyParser from "body-parser";
import { AppRoutes } from "./routes";
import { Manager } from "./manager";
createConnection().then(async (typeORMConnection) => {
const manager = new Manager();
manager.load().then(() => {
console.log("Manager has loaded all managers");
const expressApp = express();
expressApp.use(bodyParser.json());
expressApp.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "*");
res.header("Access-Control-Allow-Headers", "*");
next();
});
// Loop over every route
AppRoutes.forEach((singleRoute) => {
// Generate Express route
expressApp[singleRoute.method](singleRoute.path, (request: Request, response: Response, next: Function) => {
singleRoute.action(request, response)
.then(() => next())
.catch((error) => next(error));
});
});
// Start Express app
expressApp.listen(3000);
console.log("Express application is up and running on port 3000");
});
}).catch((error) => console.log(`TypeORM connection error: ${error}`));
A route file looks like this:
routes.ts
import { getSpeakerById, getSpeakerAll } from "./controller/get";
import { enableSpeakerById, disableSpeakerById } from "./controller/put";
export const AppRoutes = [
{
path: "/speaker",
method: "get",
action: getSpeakerAll
},
{
path: "/speaker/:id",
method: "get",
action: getSpeakerById
},
{
path: "/speaker/:id/disable",
method: "put",
action: disableSpeakerById
},
{
path: "/speaker/:id/enable",
method: "put",
action: enableSpeakerById
},
];
And last but not least this is an Express endpoint file containing the actual logic:
controller/get.ts
import { Request, Response } from "express";
import { getManager } from "typeorm";
import { Speaker } from "../entity/Speaker";
const ping = require("ping");
export async function getSpeakerById(request: Request, response: Response) {
const speakerRepository = getManager().getRepository(Speaker);
const speakerObject = await speakerRepository.findOne(request.params.id);
// If no post is found return 404
if (!speakerObject) {
response.status(404);
response.send("Speaker doesn't exist");
response.end();
return;
}
// Ping speaker and bind the time once its been resolved
speakerObject.time = await ping.promise.probe(speakerObject.host);
response.send(speakerObject);
}
export async function getSpeakerAll(request: Request, response: Response) {
const speakerRepository = getManager().getRepository(Speaker);
const speakerObjects = await speakerRepository.find();
const speakerPromise = [];
// Create a promise array of pings to all speakers
speakerObjects.forEach((speakerObject) => speakerPromise.push(ping.promise.probe(speakerObject.host)));
const speakerResults = await Promise.all(speakerPromise);
// Since the promise array is based on order we can rebind the property by looping over it in the same order
speakerResults.forEach((speakerResult, speakerIndex) => speakerObjects[speakerIndex].time = speakerResult.time);
response.send(speakerObjects);
}
Now I need to access the main Manager instance in the controller/get.ts, but I can't pass it along as parameter (for as far as I know) since it's an export. I would just import the Manager class and create a new instance but I only want to start the Manager once since it contains logic such as intervals and speaker instances from the Sonos package. I hope I was able to explain the problem but if anyone needs clarification on something I'll update the post.
You actually can pass it along as a parameter. There's nothing stopping you doing something like this in your index.ts:
// Generate Express route
expressApp[singleRoute.method](singleRoute.path, (request: Request, response: Response, next: Function) => {
singleRoute.action(request, response, manager)
.then(() => next())
.catch((error) => next(error));
});
});
And then updating the signature on you exported controller methods to be like
import { Manager } from '../manager';
export async function getSpeakerById(request: Request, response: Response, manager: Manager) {
...
}

How to setup server-side full response caching in Apollo Server

I am trying to setup a cache according to this guide for one expensive query that has a result that changes only once a day. The query takes 7-8 seconds and most it is after the DB query, because the response returned from the resolver must be heavily processed.
I am using apollo-server-express library and the change pluging is apollo-server-plugin-response-cache.
This is what I have done:
server.js
const { ApolloServer } = require('apollo-server-express')
const responseCachePlugin = require('apollo-server-plugin-response-cache')
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
// ...
},
plugins: [responseCachePlugin()]
resolvers.js
personDetails: async (root, args, ctx, info) => {
info.cacheControl.setCacheHint({ maxAge: 600 })
const persons = await Person.find({}).populate('details')
// processing the data
return processedData
}
I expect the resolver to run once and then after that the response should be returned from the cache almost instantly. This doesn't work. I am doing something wrong or I haven't understood how this should work.
I tried to put cache hints also in the schema, but didn't get any better results.
It should work. Here is a working example:
server.ts:
import { ApolloServer, gql } from 'apollo-server-express';
import express from 'express';
import responseCachePlugin from 'apollo-server-plugin-response-cache';
const typeDefs = gql`
type Query {
personDetails: String
}
`;
const resolvers = {
Query: {
personDetails: async (root, args, ctx, info) => {
console.log(`[${new Date().toLocaleTimeString()}] Query.personDetails`);
info.cacheControl.setCacheHint({ maxAge: 10 });
return 'This is person details';
},
},
};
const app = express();
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
plugins: [responseCachePlugin()],
});
apolloServer.applyMiddleware({ app });
const server = app.listen({ port: 4000 }, () => {
console.log(`The server is running in http://localhost:4000${apolloServer.graphqlPath}`);
});
The request logs:
The server is running in http://localhost:4000/graphql
[1:51:27 PM] Query.personDetails
[1:51:52 PM] Query.personDetails
The response header:
cache-control: max-age=10, public
The first graphql request send at [1:51:27 PM]. In the next 10 seconds, all requests sent will hit the cache(defaults to an in-memory LRU cache), which means the graphql resolver personDetails will not execute. The graphql response will be read from the cache and sent to client-side.
After 10 seconds, I send another graphql request at [1:51:52 PM]. The cache is expired. So this request will not hit the in-memory cache. The graphql resolver will execute and generate a new value.
source code: https://github.com/mrdulin/apollo-graphql-tutorial/tree/master/src/stackoverflow/57243105
I understand that this is an old question but it might help someone solve server side caching issue in Apollo.
So as per my understanding the apollo-server-plugin-response-cache plugin only works if the response from the underlying REST API have control-cache header. In case there is no cache-control header in your REST API it can be overriden in restDataSouce GET calls as below.
return this.get(`uri_path`,"", {cacheOptions: { ttl: 1}});
After that continue using info.cacheControl at the resolvers as suggested in the example from the question snippets. maxAge there will override the time mentioned in the cacheOptions.

Categories

Resources