NestJS backend as a microservice and an API Rest the same time? - api

Can a NestJS backend be a microservice and an API Rest as the same time ?
main.ts for a microservice:
const microservice = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.KAFKA,
// ...options, client, consumer, etc...
},
);
await microservice.listen();
main.ts for a REST API:
const app = await NestFactory.create(AppModule)
await app.listen(3000);
How to mix use create and createMicroservice in the same main.ts ?
Should I use a Gateway API with the serviceA as microservice and serviceB as REST API ?

Pretty sure what you're looking for is a hybrid application. You create the regular HTTP application with NestFactory.create() and then you use app.connectMicroservice() to add microservices that should run alongside the HTTP server.
Example from the docs:
const app = await NestFactory.create(AppModule);
// microservice #1
const microserviceTcp = app.connectMicroservice<MicroserviceOptions>({
transport: Transport.TCP,
options: {
port: 3001,
},
});
// microservice #2
const microserviceRedis = app.connectMicroservice<MicroserviceOptions>({
transport: Transport.REDIS,
options: {
host: 'localhost',
port: 6379,
},
});
await app.startAllMicroservices();
await app.listen(3001);

Related

API Requests that need Authorization return CORS error

Initial Problem
I work on a web application (react) that accesses data via an API. The API runs for development reasons on a docker container on my local machine. Simple GET requests (via axios) got me CORS complications (...has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.).
A bit of researching solved my problem by running a nginx reverse proxy in another container. I basically used this configuration for the nginx server.
New Problem
As I progress in building my application, I come to a point where I need to send the JWT to the API to access and alter some entries. Requests that need sending a JWT again get me CORS error messages.
The API checks the JWT signature (RS256 generated). I just have to forward it to the API server.
ALSO: simple curl requests with the JWT from the console are working.
Configuration
axios
const axiosConfig = {
responseType: "json",
withCredentials: false,
mode: "no-cors",
headers: {
'Access-Control-Allow-Origin': "*",
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE,PATCH,OPTIONS',
'Content-Type': 'application/json',
'Authorization': 'Bearer <JWT as string>',
},
};
const apiGetRequest = async (route, callback) => {
try {
const apiUrl = URL + route;
axios.get(apiUrl, {
axiosConfig
})
.then(res => {
callback(res);
})
.catch(function (error) {
console.log(error);
});
} catch (error) {
console.log(error);
}
}
nginx configuration
Docker Image for api
version: "3.9"
services:
db:
image: mariadb:latest
container_name: db
env_file:
- ./mariadb/.env
volumes:
- ./mariadb/create-schema-docker.sh /docker-entrypoint-initdb.d
- db-data:/var/lib/mysql
ports:
- 3306:3306
rest:
image: mds4ul/station-registry:latest
container_name: api
environment:
- DB_HOST=db
- CONTEXT_PATH=api
env_file:
- ./rest/.env
depends_on:
- db
ports:
- 80:8080
volumes:
db-data:
Questions
Why do I get CORS errors for requests where a jwt is needed and not for requests that do not require one?
Which part do I have to change to make this work?
So answer another question to an embarrassing easy problem of mine.
I switched to an express.js proxy server with the following configuration:
const express = require('express')
const app = express()
const axios = require('axios')
const cors = require('cors')
var bodyParser = require('body-parser')
app.use(cors({
origin: '*'
}))
app.use(bodyParser.json())
require('dotenv').config()
const headers = {
"X-Authorization": <token>,
}
app.get(':endpoint([\\/\\w\\.-]*)', function (req, res) {
const endpoint = (process.env.API_BASE_URL).replace(/\/$/, "") + req.params.endpoint;
axios.get(endpoint, { headers }).then(response => {
res.json(response.data)
}).catch(error => {
res.json(error)
})
})
app.listen(3001)
I assume I just could not figure out my nginx configuration for this use case. So with express.js I can access now resources which need authorization.

Market data routing to frontend: Alpaca WebSocket -> Node.js WebSocket -> React Frontend

I'm trying to use the websocket example from:
https://github.com/alpacahq/alpaca-trade-api-js/blob/master/examples/websocket_example_datav2.js
In order to connect to the Alpaca V2 data stream. Currently, my stream is working but I'm trying to route my data to the client side using Server Sent Events. My data flow seems like it should be:
Alpaca Data Stream API -> My Node.js server -> React Frontend.
The issue I have is using the DataStream object in the example in order to route the data to the frontend. Since, with the object alone, I don't have any route to subscribe to via Server Sent Events, does this mean that I should also be using either express, socket.io, or ws? Since the all of the ".on_xyz" methods are defined within the DataStream object, I'm not sure how to set up the endpoint properly to allow my frontend to subscribe to it. If anyone knows how to route this datastream information forward it would be greatly appreciated- I'm particularly trying to work with the .onStockQuote method but any of them is fine! I'm simply trying to use Node as an inbetween router so that I don't have to subscribe directly from the frontend (and not use the sdk), because that limits scalability of the API's use.
"use strict";
/**
* This examples shows how to use tha alpaca data v2 websocket to subscribe to events.
* You should use the alpaca api's data_steam_v2, also add feed besides the other parameters.
* For subscribing (and unsubscribing) to trades, quotes and bars you should call
* a function for each like below.
*/
import express from 'express';
const app = express()
const Alpaca = require("#alpacahq/alpaca-trade-api");
const API_KEY = "XYZ_Key";
const API_SECRET = "XYZ_Secret";
const PORT = 3000;
// Add a new message and send it to all subscribed clients
const addMessage = (req, res) => {
const message = req.body;
// Return the message as a response for the "/message" call
res.json(message);
return ;
};
class DataStream {
constructor({ apiKey, secretKey, feed }) {
this.alpaca = new Alpaca({
keyId: apiKey,
secretKey,
feed,
});
const socket = this.alpaca.data_stream_v2;
socket.onConnect(function () {
console.log("Connected");
socket.subscribeForQuotes(["AAPL"]);
// socket.subscribeForTrades(["FB"]);
// socket.subscribeForBars(["SPY"]);
// socket.subscribeForStatuses(["*"]);
});
socket.onError((err) => {
console.log(err);
});
socket.onStockTrade((trade) => {
console.log(trade);
});
socket.onStockQuote((quote) => {
console.log(quote);
});
socket.onStockBar((bar) => {
console.log(bar);
});
socket.onStatuses((s) => {
console.log(s);
});
socket.onStateChange((state) => {
console.log(state);
});
socket.onDisconnect(() => {
console.log("Disconnected");
});
socket.connect();
// unsubscribe from FB after a second
// setTimeout(() => {
// socket.unsubscribeFromTrades(["FB"]);
// }, 1000);
}
}
app.post("/message", addMessage);
let stream = new DataStream({
apiKey: API_KEY,
secretKey: API_SECRET,
feed: "sip",
paper: false,
});
app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
});

How to use Botkit v4+ with custom express server for Slack bot

For Botkit v0.7.4, a custom express server can be used as follows
module.exports = function(webserver, controller) {
webserver.post('/slack/receive', function(req, res) {
res.status(200);
res.send('ok');
controller.handleWebhookPayload(req, res);
});
return webserver;
}
is something similar available for the latest version also.
Got the above sample from here
So far, I've got this
const adapter: SlackAdapter = new SlackAdapter({
getTokenForTeam,
getBotUserByTeam,
clientId: config.get('slack.clientId'),
clientSecret: config.get('slack.clientSecret'),
clientSigningSecret: config.get('slack.signingSecret'),
scopes: ['bot', 'team:read', 'users:read', 'users:read.email', 'chat:write:bot'],
redirectUri: config.get('slack.redirectUri')
});
adapter.use(new SlackEventMiddleware());
adapter.use(new SlackMessageTypeMiddleware());
// webserver is an express app - like so - const webserver = express();
const controller = new Botkit({
adapter,
webserver,
webhook_uri: '/slack/receive'
});
controller.ready(() => controller.loadModules('./features'));
How to setup the /slack/receive route so that the challenge verification when activating the events API and all further events emitted from Slack will be handled properly

Store Socket IO socket.id in Express session

I have a web app using Angular and Express. I have a rest api for database updates and I am also using SocketIO to update the clients in realtime.
I am tracking a list of active socket IDs for each user but now I would like to have access to the clients socket id in my express route so that I can emit a message to all other users (equivalent of socket.broadcast)
I'm trying to store the socket ID in an expression session so I can access it in my route but i've not been able to get it working. In the code below i'm logging my session when the socket connects and this shows the socketio key i've added but in the /jobs request the socketio key is undefined..
My server.ts is something like this:
import * as expsession from 'express-session'
// create Express app variable
const app = express()
// create HTTP server
const server = http.createServer(app);
// set up CORS so it can work Angular application at another domain/port
app.use(cors({
origin: [ "http://localhost:4200" ],
credentials: true
}))
// install session middleware
const session = expsession({
secret: 'random secret',
saveUninitialized: true,
resave: true,
cookie: { secure: false }
});
// run session middleware for regular http connections
app.use(session);
// *** SOCKET IO *** //
const io = require('socket.io')(server);
// run session middleware for socket.io connections
io.use((socket, next) => {
session(socket.request, socket.request.res || {}, next);
});
// when a socket.io connection connects, put the socket.id into the session
// so it can be accessed from other http requests from that client
io.on('connection', (socket) => {
console.log(`socket.io connected: ${socket.id}`);
// save socket.io socket in the session
socket.request.session.socketio = socket.id;
socket.request.session.save();
console.log("socket.io connection, session:\n", socket.request.session);
});
app.get('/jobs', (req, res) => {
const session = req.session;
console.log("JOBS: session at '/jobs':\n", session);
Job.getJobs((err, jobs) => {
if (err) {
res.json({success: false, msg: err});
} else {
res.json(jobs);
}
});
});
I'm also including credentials in my angular service request, e.g.:
this.http.get(api_url + '/jobs', {withCredentials:true}).subscribe((jobs:IJob[]) => {
this.jobs$.next(jobs)
})
When a socket is connected an id is created(socket.id) and by default it joins to a room with that id. If you already have id for your client just send it to the server and join to a room with the client id.
socket.leave(socket.id);//leaving default room
socket.join(my_custom_id);//joining to custom id room
the custom id will appear in client side as the websocket7 id.
If you still don't know how to get the id in your express route; use a jsonwebtoken with the id and decoded in your route, thats it.
Example:
var bearer = req.headers.authorization.split(" ")[1];//this is node, get headers with express way
var auth_data = jwt.decodeToken( bearer );
decoding:
var jwt = require('jsonwebtoken');
var secret = 'your_secret';
function decodeToken(token){
try {
var decoded = jwt.verify( token , secret );
return decoded;
} catch (error) {
console.log('jwt', error);
return null;
}
}
encoding:
//data is an object with data you want to encode, id, etc
function createJsonWebToken(data){
var token = jwt.sign( data, secret );
return token
}
/*after encoding you send this token to the
client, then you send it back to the server
to the routes you need the data, then just
decoded it in that route*/

Apollo-server-express implemented with Apollo federation

Has anyone succesfully implemented an apollo express server (as the gateway) together with a federation of graphQL servers?
My means of authentication is present via a REST endpoint on my gateway so I am running an apollo-express server as a top layer, but when trying to query the data it doesn't return anything from the other services. When I switch to having the gateway operate as purely an Apollo server, then it functions and can get data from the services belonging to the apollo federation connected to it. But this means I have to disregard my implementation of authentication and other REST endpoints I am using with the service.
Current setup:
const main = async () => {
try {
const app = express();
app.use(function(_req, res, next) {
res.header("Access-Control-Allow-Origin", `${config.frontend_url}`);
res.header('Access-Control-Allow-Methods', "GET,HEAD,OPTIONS,POST,PUT");
res.header("Access-Control-Allow-Credentials", "true");
res.header('Access-Control-Allow-Headers', "Access-Control-Allow-Origin, Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization");
next();
});
app.use(cookieParser());
app.use(cookieSession({
maxAge: 24 * 60 * 60 * 1000,
keys: [config.cookieKey]
}));
app.use('/auth', loginRouter);
const serviceList = [
{name: "service", url: config.service_url + "/graphql"},
{name: "service", url: config.service_url + "/graphql"},
]
const gateway = new ApolloGateway({
serviceList,
});
const { schema, executor } = await gateway.load();
// const schema = await createSchema();
app.use(
'/graphql',
checkToken,
graphqlHTTP(req => ({
schema,
graphiql: true,
context: req
}))
);
app.use('/', ((_req, res) => {
res.sendStatus(200);
}));
const apolloServer = new ApolloServer({ schema, executor, playground: true, tracing: false , subscriptions: false}) as any;
apolloServer.applyMiddleware({ app });
app.listen(config.port, () => {
console.log(`🚀 Server ready at api.backoffice.myos.co:${config.port}${apolloServer.graphqlPath}`)
});
} catch (e) {
Logger.error(">>> >>> >>> createSchema() error:", e);
}
};