Angular 2 Universal + Akamai - node.js

I want to see if I can create a stack based on both, CDN and also angular 2 universal. So when the user navigate has the CDN to get the assets, and if the user access the first time will have the complete html rendered by Universal.
I was thinking in:
Client <===> Akamai <===> Varnish <===> Origin Server (node.js with universal)
This sounds good? have you ever tried it?
Also i'm considering adding nginx and ELB for the complete stack.
The question is:
- Can this stack work as expected?

Yes it can be done! The big issue is how do you invalidate an arbitrary number of http requests made in Angular that determine the rendered page. Using some sort of header schema to invalidate might be helpful.
Assuming your are using official ng-express engine, a service like this could let you define the response from the Angular runtime:
import { RESPONSE } from '#nguniversal/express-engine/tokens'
import { Inject, Injectable, Optional } from '#angular/core'
import { Response } from 'express'
export interface IServerResponseService {
getHeader(key: string): string
setHeader(key: string, value: string): this
setHeaders(dictionary: { [key: string]: string }): this
appendHeader(key: string, value: string, delimiter?: string): this
setStatus(code: number, message?: string): this
setNotFound(message?: string): this
setError(message?: string): this
}
#Injectable()
export class ServerResponseService implements IServerResponseService {
private response: Response
constructor(#Optional() #Inject(RESPONSE) res: any) {
this.response = res
}
getHeader(key: string): string {
return this.response.getHeader(key)
}
setHeader(key: string, value: string): this {
if (this.response)
this.response.header(key, value)
return this
}
appendHeader(key: string, value: string, delimiter = ','): this {
if (this.response) {
const current = this.getHeader(key)
if (!current) return this.setHeader(key, value)
const newValue = [...current.split(delimiter), value]
.filter((el, i, a) => i === a.indexOf(el))
.join(delimiter)
this.response.header(key, newValue)
}
return this
}
setHeaders(dictionary: { [key: string]: string }): this {
if (this.response)
Object.keys(dictionary).forEach(key => this.setHeader(key, dictionary[key]))
return this
}
setStatus(code: number, message?: string): this {
if (this.response) {
this.response.statusCode = code
if (message)
this.response.statusMessage = message
}
return this
}
setNotFound(message = 'not found'): this {
if (this.response) {
this.response.statusCode = 404
this.response.statusMessage = message
}
return this
}
setError(message = 'internal server error'): this {
if (this.response) {
this.response.statusCode = 500
this.response.statusMessage = message
}
return this
}
}

Related

I am tryng to use an api to get data, but the async function its not wroking

i am tryng to get the data on the main file of a react-native aplication, so i can manipulate it and use it to create the app, but i dont know wath i am doing wrong. Let me explain:
The Api its working, normaly, i did the "get method" whit axios in the file "APP.tsx and it returned what i wanted, i also tryed request with insominia. Then i thougth it wold be a better idea to separe in a diferent file, because i have the intention to create several calls from other requests, and possible from others api's. Then i did this:
this is the App.tsx file resumed without the components:
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, View, Text } from 'react-native';
import React, { useEffect, useState } from 'react';
import Header from './src/components/header/Header';
import { GamesToUseProps } from './src/services/getGames/apiGetGamesData';
import {useGames} from './src/services/getGames/apiGetGamesData';
export default function App() {
const [games, setGames] = useState<GamesToUseProps[] | null>(null);
const [test, setTest] = useState('asc');
useEffect(() => {
const fetchData = async () => {
setTest('teste');
const gamesData = await useGames();
setGames(gamesData);
};
fetchData();
}, []);
return (
)
}
Then i created i this (.ts) file to manipulate the data that come from the api:
import { useState } from 'react'; import { setupApiGames } from '../setupApi';
export interface Game { fixture_id: string; event_timestamp: string; event_date: string; league_id: string; round: string; homeTeam_id: string; awayTeam_id: string; homeTeam: string; awayTeam: string; status: string; statusShort: string; goalsHomeTeam: string | null; goalsAwayTeam: string | null; halftime_score: string | null; final_score: string | null; penalty: string; elapsed: string; firstHalfStart: string; secondHalfStart: string; }
export interface GamesToUseProps { fixture_id: string; event_timestamp: string; event_date: string; league_id: string; round: string; homeTeam_id: string; awayTeam_id: string; homeTeamLogo: string; homeTeam: string; awayTeamLogo: string; awayTeam: string; status: string; statusShort: string; goalsHomeTeam: string | null; goalsAwayTeam: string | null; halftime_score: string | null; final_score: string | null; penalty: string; elapsed: string; firstHalfStart: string; secondHalfStart: string; }
export function useGames(): GamesToUseProps[] { const [games, setGames] = useState<Game[]>([]); const [gamesToUse, setGamesToUse] = useState<GamesToUseProps[]>([]);
async function getGames() {
try {
const apiGames = setupApiGames();
const response = await apiGames?.get('/v2/fixtures/league/524/next/10');
setGames(response?.data);
} catch (error) {
console.log("Erro ao tentar acessar os dados da API " + error);
}
} getGames;
if(games) { const data = games.map(game => ({ fixture_id: game.fixture_id, event_timestamp: game.event_timestamp, event_date: game.event_date, league_id: game.league_id, round: game.round, homeTeam_id: game.homeTeam_id, awayTeam_id: game.awayTeam_id, homeTeamLogo: https://media.api-sports.io/football/teams/${game.homeTeam_id}.png, homeTeam: game.homeTeam, awayTeamLogo: https://media.api-sports.io/football/teams/${game.awayTeam_id}.png, awayTeam: game.awayTeam, status: game.status, statusShort: game.statusShort, goalsHomeTeam: game.goalsHomeTeam, goalsAwayTeam: game.goalsAwayTeam, halftime_score: game.halftime_score, final_score: game.final_score, penalty: game.penalty, elapsed: game.elapsed, firstHalfStart: game.firstHalfStart, secondHalfStart: game.secondHalfStart, }));
setGamesToUse(data);
}
return gamesToUse;
}
so i use this two other files (.ts) to set the api configuration:
i have the intention to use this one help me to get things more easy when i have more requests.
import { getGames } from './apiGetData';
export function setupApiGames() { const apiGames = getGames();
return apiGames;
}
// export function setupApiTeams() {
// const apiTeams = getTeams();
// return apiTeams; // }
this one it is the main one, whith the configuration from axios:
import axios from "axios";
export function getGames() {
try {
const apiGames = axios.create({
baseURL: 'https://api-football-v1.p.rapidapi.com/',
headers: {
'X-RapidAPI-Key': 'keyHere',
'X-RapidAPI-Host': 'api-football-v1.p.rapidapi.com'
}
})
return apiGames;
} catch (error) {
console.log(error + "Erro ao tentar acessar os dados da API");
}
}
So, i tryed to debug, searched and web and a dont know whats its happening, i only noticed that the async function on the file App.tsx its not getting the data. everything that cames after "const gamesData = await useGames(), kind that donsent work, its kind it is waiting for ever, and never get it. i hope you cold help me, i also that you could be a litte comprehnsive too, i am a inexperient programmer tryng to become a relevant. i alredy thank for your attention and help and also, sorry for my english.

How to add JSON data to Jetstream strams?

I have a code written in node-nats-streaming and trying to convert it to newer jetstream. A part of code looks like this:
import { Message, Stan } from 'node-nats-streaming';
import { Subjects } from './subjects';
interface Event {
subject: Subjects;
data: any;
}
export abstract class Listener<T extends Event> {
abstract subject: T['subject'];
abstract queueGroupName: string;
abstract onMessage(data: T['data'], msg: Message): void;
private client: Stan;
protected ackWait = 5 * 1000;
constructor(client: Stan) {
this.client = client;
}
subscriptionOptions() {
return this.client
.subscriptionOptions()
.setDeliverAllAvailable()
.setManualAckMode(true)
.setAckWait(this.ackWait)
.setDurableName(this.queueGroupName);
}
listen() {
const subscription = this.client.subscribe(
this.subject,
this.queueGroupName,
this.subscriptionOptions()
);
subscription.on('message', (msg: Message) => {
console.log(`Message received: ${this.subject} / ${this.queueGroupName}`);
const parsedData = this.parseMessage(msg);
this.onMessage(parsedData, msg);
});
}
parseMessage(msg: Message) {
const data = msg.getData();
return typeof data === 'string'
? JSON.parse(data)
: JSON.parse(data.toString('utf8'));
}
}
As I searched through the documents it seems I can do something like following:
import { connect } from "nats";
const jsm = await nc.jetstreamManager();
const cfg = {
name: "EVENTS",
subjects: ["events.>"],
};
await jsm.streams.add(cfg);
But it seems there are only name and subject options available. But from my original code I need a data property it can handle JSON objects. Is there a way I can convert this code to a Jetstream code or I should change the logic of the whole application as well?

How to pass a child Interface to a parent class?

I have this:
LocationController.ts
import {GenericController} from './_genericController';
interface Response {
id : number,
code: string,
name: string,
type: string,
long: number,
lat: number
}
const fields = ['code','name','type','long','lat'];
class LocationController extends GenericController{
tableName:string = 'location';
fields:Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
}
export {get};
GenericController.ts
interface Response {
id : number
}
export class GenericController{
tableName:string = '';
fields:Array<any> = [];
_get = async (req, res) => {
try{
const id = req.body['id'];
const send = async () => {
const resp : Array<Response> = await db(this.tableName).select(this.fields).where('id', id)
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
}
await send();
}catch (error){
// some error handling
}
}
}
What I want to do is to pass the Response interface from LocationController to the GenericController parent, so that the response is typed accurately depending on how the child class has defined it. Clearly it doesn't work like this since the interface is defined outside of the class so the parent has no idea about the Response interface in the LocationController.ts file.
I've tried passing interface as an argument in the constructor, that doesn't work. So is there a way I can make this happen? I feel like I'm missing something really simple.
Typically, generics are used in a situation like this. Here's how I'd do it:
interface Response {
id: number;
}
// Note the generic parameter <R extends Response>
export class GenericController<R extends Response> {
tableName: string = "";
fields: Array<any> = [];
_get = async (req, res) => {
try {
const id = req.body["id"];
const send = async () => {
// The array is now properly typed. You don't know the exact type,
// but you do know the constraint - R is some type of `Response`
let resp: Array<R> = await db(this.tableName).select(this.fields).where("id", id);
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
};
await send();
} catch (error) {
// some error handling
}
};
}
import { GenericController } from "./_genericController";
interface Response {
id: number;
code: string;
name: string;
type: string;
long: number;
lat: number;
}
const fields = ["code", "name", "type", "long", "lat"];
// Here we tell the GenericController exactly what type of Response it's going to get
class LocationController extends GenericController<Response> {
tableName: string = "location";
fields: Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
};
export { get };
If this is not enough and you wish to somehow know the exact response type you're going to get, I believe the only way is a manual check. For example:
import { LocationResponse } from './locationController';
// ... stuff
// Manual runtime type check
if (this.tableName === 'location') {
// Manual cast
resp = resp as Array<LocationResponse>
}
// ...
You could also check the form of resp[0] (if (resp[0].hasOwnProperty('code')) { ... }) and cast accordingly. There are also nicer ways to write this, but the basic idea remains the same.
Generally, a properly written class should be unaware of any classes that inherit from it. Putting child-class-specific logic into your generic controller is a code smell. Though as always, it all depends on a particular situation.

How to Type Fastify Reply payloads?

I'm just getting into Fastify with Typescript and really enjoying it.
However, I'm trying to figure out if I can type the response payload. I have the response schema for serialization working and that may be sufficient, but I have internally typed objects (such as IUser) that it would be nice to have Typescript check against.
The following works great, but I'd like to return an TUser for example and have typescript if I return something different. Using schema merely discludes fields.
interface IUser {
firstname: string,
lastname: string
} // Not in use in example
interface IUserRequest extends RequestGenericInterface {
Params: { username: string };
}
const getUserHandler = async (
req: FastifyRequest<IUserRequest, RawServerBase, IncomingMessage | Http2ServerRequest>
) => {
const { username } = req.params;
return { ... }; // Would like to return instance of IUser
};
app.get<IUserRequest>('/:username', { schema }, helloWorldHandler);
Is there an equivalent of RequestGenericInterface I can extend for the response?
Small Update: It seems that the reply.send() can be used to add the type, but it would be nice for self-documentation sake to provide T higher up.
From the documentation:
Using the two interfaces, define a new API route and pass them as generics. The shorthand route methods (i.e. .get) accept a generic object RouteGenericInterface containing five named properties: Body, Querystring, Params, Headers and Reply.
You can use the Reply type.
interface MiscIPAddressRes {
ipv4: string
}
server.get<{
Reply: MiscIPAddressRes
}>('/misc/ip-address', async (req, res) => {
res
.status(_200_OKAY)
.send({
ipv4: req.ip // this will be typechecked
})
})
After looking at the type definitions, I found out that there is also an alternative way to only type-check the handler (like in Julien TASSIN's answer), like this:
import { FastifyReply, FastifyRequest, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault } from "fastify";
import { RouteGenericInterface } from "fastify/types/route";
interface IUser {
firstname: string;
lastname: string;
}
interface IUserRequest extends RouteGenericInterface {
Params: { username: string };
Reply: IUser; // put the response payload interface here
}
function getUserHandler(
request: FastifyRequest<IUserRequest>,
reply: FastifyReply<
RawServerDefault,
RawRequestDefaultExpression,
RawReplyDefaultExpression,
IUserRequest // put the request interface here
>
) {
const { username } = request.params;
// do something
// the send() parameter is now type-checked
return reply.send({
firstname: "James",
lastname: "Bond",
});
}
You can also create your own interface with generic to save writing repeating lines, like this:
import { FastifyReply, FastifyRequest, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault } from "fastify";
import { RouteGenericInterface } from "fastify/types/route";
export interface FastifyReplyWithPayload<Payload extends RouteGenericInterface>
extends FastifyReply<
RawServerDefault,
RawRequestDefaultExpression,
RawReplyDefaultExpression,
Payload
> {}
then use the interface like this:
function getUserHandler(
request: FastifyRequest<IUserRequest>,
reply: FastifyReplyWithPayload<IUserRequest>
) {
const { username } = request.params;
// do something
// the send() parameter is also type-checked like above
return reply.send({
firstname: "James",
lastname: "Bond",
});
}
If you want to type the handler only, you can perform it this way
import { RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault, RouteHandler, RouteHandlerMethod } from "fastify";
const getUserHandler: RouteHandlerMethod<
RawServerDefault,
RawRequestDefaultExpression,
RawReplyDefaultExpression,
{ Reply: IUser; Params: { username: string } }
> = async (
req: FastifyRequest<IUserRequest, RawServerBase, IncomingMessage | Http2ServerRequest>
) => {
const { username } = req.params;
return { ... }; // Would like to return instance of IUser
};
Trying to type these was a truely awful experience. Thanks to the other answers, this is where I ended up. Bit of a code dump to make life easier for others.
request-types.ts
With this I am standardising my response to optionally have data and message.
import {
FastifyReply,
FastifyRequest,
RawReplyDefaultExpression,
RawRequestDefaultExpression,
RawServerDefault,
} from 'fastify';
type ById = {
id: string;
};
type ApiRequest<Body = void, Params = void, Reply = void> = {
Body: Body;
Params: Params;
Reply: { data?: Reply & ById; message?: string };
};
type ApiResponse<Body = void, Params = void, Reply = {}> = FastifyReply<
RawServerDefault,
RawRequestDefaultExpression,
RawReplyDefaultExpression,
ApiRequest<Body, Params, Reply>
>;
type RouteHandlerMethod<Body = void, Params = void, Reply = void> = (
request: FastifyRequest<ApiRequest<Body, Params, Reply>>,
response: ApiResponse<Body, Params, Reply>
) => void;
export type DeleteRequestHandler<ReplyPayload = ById> = RouteHandlerMethod<void, ById, ReplyPayload>;
export type GetRequestHandler<ReplyPayload> = RouteHandlerMethod<void, ById, ReplyPayload>;
export type PostRequestHandler<Payload, ReplyPayload> = RouteHandlerMethod<Payload, void, ReplyPayload>;
export type PatchRequestHandler<Payload, ReplyPayload> = RouteHandlerMethod<Payload, ById, ReplyPayload>;
export type PutRequestHandler<Payload, ReplyPayload> = RouteHandlerMethod<Payload, ById, ReplyPayload>;
Usage
get-account.ts - GetRequestHandler
export const getAccount: GetRequestHandler<AccountResponseDto> = async (request, reply) => {
const { id } = request.params;
...
const account = await Account.findOne....
...
if (account) {
return reply.status(200).send({ data: account });
}
return reply.status(404).send({ message: 'Account not found' });
};
delete-entity.ts - DeleteRequestHandler
export const deleteEntity: DeleteRequestHandler = async (request, reply) => {
const { id } = request.params;
...
// Indicate success by 200 and returning the id of the deleted entity
return reply.status(200).send({ data: { id } });
};
update-account.ts - PatchRequestHandler
export const updateAccount: PatchRequestHandler<
UpdateAccountRequestDto,
AccountResponseDto
> = async (request, reply) => {
const { id } = request.params;
...
return reply.status(200).send({ data: account });
};
register-account-routes.ts - No errors with provided handler.
export const registerAccountRoutes = (app: FastifyInstance) => {
app.get(EndPoints.ACCOUNT_BY_ID, getAccount);
app.patch(EndPoints.ACCOUNT_BY_ID, updateAccount);
app.post(EndPoints.ACCOUNTS_AUTHENTICATE, authenticate);
app.put(EndPoints.ACCOUNTS, createAccount);
};

Angular2 - Handling API Response

Good afternoon! I'm new in Angular 2, so I'm sorry in advance if my question is generic. I cannot figure out how to handle an API response.
My NodeJS Server API function is (Checked and works fine):
router.get('/appointment/:iatreio/:time', function(req, res, next) {
var paramIatreio = req.params.iatreio;
var paramTime = req.params.time;
db.appointments.findOne({iatreio: paramIatreio, time: req.params.time}, function(err, resultFound) {
if (err) { res.send(err); }
if (resultFound) {
res.json(true); // 1st Question: For best practice, res.json(true) or res.send(true)?
} else {
res.json(false);
}
});
});
My Angular2 Service:
import { Injectable } from '#angular/core';
import { Headers , Http } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class AppointmentService {
constructor(private http: Http) { }
isBooked(iatreio: string, time: string): Observable<boolean> {
return this.http
.get('http://localhost:3000/appointment/'+iatreio+'/'+time)
.map(); //2nd Question: What inside map()?
}
} // end of Service
Component Function
isBooked(selectedIatreio: string, selectedTime: string): boolean {
this.appointmentService
.isBooked(selectedIatreio, selectedTime)
.subscribe(() => {}); //3rd Question: What inside subscribe()?
}
My final goal is the "isBooked(...)" function of my Component to be called and to return true or false. I have seen the code in the examples in the Angular2 site, but I'm a little confused on my case.
Can Service function return directly a true or false value or it has to be an Observable?? Map() function is necessary??
Generally, my thinking is right?? Or my goal can be accomplished more easily??
Thank you a lot for your time!!
map is used to convert the response into the model which you look for
isBooked(iatreio: string, time: string): Observable<boolean> {
return this.http
.get('http://localhost:3000/appointment/'+iatreio+'/'+time)
.map((response)=><boolean>response.json());
}
subscribe will return the data emitted by the service
isBooked(selectedIatreio: string, selectedTime: string): boolean {
this.appointmentService
.isBooked(selectedIatreio, selectedTime)
.subscribe((data) => {
//your operation
console.log(data);
});
}

Resources