Access extended class' properties and methods with TypeScript decorators - node.js

I'm working on an experimental refactoring of my express app and chose to go with TypeScript as I prefer strong typing with any language. I saw the potential of using TypeScript's decorators as a powerful method to build a well-structured project as DRY as possible.
I'm having issues in accessing a property from a class that is extended by a class where I have the decorator set up.
example:
class BaseRouter {
app: express.Application = express;
// some other common stuff
}
function PostMethod(route: string = '/') {
return (target: BaseRouter, key: string, descriptor: PropertyDescriptor): void {
// This is where things don't work out
// descriptor.value correctly returns the RequestHandler which I can attach to express
// target.app is undefined
target.app.post(route, descriptor.value);
}
}
router.ts
#ResourceRouter() // <= this is optional, can I access all decorators inside the class from this decorator? That would also lead me to a solution
export class BlogRouter extends BaseRouter {
#GetMethod()
index(req, res) {
// req.send(...); return posts
}
#GetMethod('/:modelId')
show(req, res, next) {
// find req.params.modelId
}
#PostMethod()
createPost() {}
#DeleteMethod()
deletePost() {}
#UpdateMethod()
updatePost() {}
}
I know this is not perfect I'm just experimenting with decorators at the moment as I have successfully used them in other scenarios that works really well. Note that this is not in any way Angular 2+ related.

Related

I can't understand how do 'global`s work in TypeScript/NodeJS and what is their difference?

I am reading a code like below:
import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
import request from "supertest";
import { app } from "../app";
declare global {
function signin(): Promise<string[]>;
}
let mongo: any;
beforeAll(async () => {
process.env.JWT_KEY = "asdfasdf";
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const mongo = await MongoMemoryServer.create();
const mongoUri = mongo.getUri();
await mongoose.connect(mongoUri, {});
});
beforeEach(async () => {
const collections = await mongoose.connection.db.collections();
for (let collection of collections) {
await collection.deleteMany({});
}
});
afterAll(async () => {
if (mongo) {
await mongo.stop();
}
await mongoose.connection.close();
});
global.signin = async () => {
const email = "test#test.com";
const password = "password";
const response = await request(app)
.post("/api/users/signup")
.send({
email,
password,
})
.expect(201);
const cookie = response.get("Set-Cookie");
return cookie;
};
I can't understand the purpose of global.signin function and how does it work? I guess it has something to do with Jest but as long as I know the Jest codes should be inside the __test__ folder with the same file name and .test.ts extension. But the above function is defined and used inside the setup.ts file in the root of the application.
I also see some codes like following:
declare global {
namespace Express {
interface Request {
currentUser?: UserPayload;
}
}
}
In some .ts files of the project as well that I am not sure are these global variables the same as the other globals I mentioned above or these are different things? I am interested to know how this global variables work as well?
The piece of code you shared is making use of global augmentation https://www.typescriptlang.org/docs/handbook/declaration-merging.html#global-augmentation
// Hint typescript that your global object will have a custom signin function
declare global {
function signin(): Promise<string[]>;
}
// Assign value to global.signin
global.signin = async () => { /* implementation */ };
Likely one or multiple modules ("mongoose", "supertest", "../app") imported by the test file is using global.signin (or window.signin) at some point (or maybe one of their nested imports is => look for "signin(" in the project). Thus for testing purposes, global.signin needed to be mocked. However just adding global.signin = something would raise a typescript error, because signin is not a standard global variable. This is where declare global comes into play. It hints typescript that in your particular context, a signin function is expected to exist in global scope.
JavaScript/TypeScript running in node will try to resolve anything it can't find in the current local scope in global (the same way a browser would look in window). Any function or variable you can access globally (e.g. setTimeout()), can also be accessed with global. as prefix. It just makes it explicit.
What happens in your code are two things:
declare global {
function signin(): Promise<string[]>;
}
Here it tells typescript's type system that the global object also has a function called signin. This part is not required but it makes sense required for typescript to allow you to access / define that function, in JavaScript you simply define it.
https://www.typescriptlang.org/docs/handbook/declaration-merging.html has some details how declare works.
global.signin = async () => {
// code ...
};
And here it is actually added to the global object.
In JavaScript non strict mode you could even write (notice the lack of var/let/const/global.)
signin = async () => {
// code ...
};
I don't see signin getting used anywhere in that code so the reason for it is unclear to me. As long as the file that defines it gets loaded you can call the function simply by referring to it as signin(). The global. is added implicitly.
The purpose of
declare global {
namespace Express {
interface Request {
currentUser?: UserPayload;
}
}
}
is more practical, in express you may want to add properties to your requests that get added by middleware. By declaring that the Express Request has a property called currentUser you get to do
app.get((req, res) => {
const user: UserPayload = req.currentUser
...
})
without typescript complaining about an unknown property.
More on that for example https://blog.logrocket.com/extend-express-request-object-typescript/

How to separately type Fastify decorators in different plugins?

I'm having trouble typing decorators in two plugins (scopes):
import Fastify, { FastifyInstance } from 'fastify'
const fastify = Fastify()
// scope A
fastify.register((instance) => {
instance.decorate('utilA', 'AAA')
instance.get('/', (req, reply) => {
const data = instance.utilA // Property 'utilA' does not exist on type 'FastifyInstance<...>'
reply.send(data)
})
}, { prefix: '/A/' })
// scope B
fastify.register((instance) => {
instance.decorate('utilB', () => 'BBB')
instance.get('/', (req, reply) => {
const data = instance.utilB() // Property 'utilB' does not exist on type 'FastifyInstance<...>'
reply.send(data)
})
}, { prefix: '/B/' })
I can define types globally:
declare module 'fastify' {
interface FastifyInstance {
utilA: string
utilB: () => string
}
}
And it solves the compiler errors. But since utilA and utilB are defined globally compiler allows me to use utilB in the scope A and vice versa.
Is there a way to do the same but independently per scope?
No I do not think this is possible with the declare module '' syntax due to the way that TypeScript works (although someone smarter than me might be able to figure out how).
One thing you can do is create a sub type of the FastifyInstance with your new properties (they will need to be optional or TypeScript might get mad at you depending on your tsconfig settings) and type your plugins fastify instance with that type. For example:
// Scope A
import { FastifyInstance } from 'fastify';
type MyPluginsFastifyInstance = FastifyInstance & { myThing?: MyType; }
export default async (fastify: MyPluginFastifyInstance) => {
... // Implementation.
fastify.myThing // TypeScript is happy :)
}
// Scope B
export default async (fastify) => {
fastify.myThing // TypeScript is mad at you >:(
}

How to use Winston Logger in NestJS separate module that doesn't use Classes

I've tried a few different ways of doing this.
I can't set Winston as the default logger for NestJS at the moment because it complains about "getTimestamp" function not being in the instance.
So - for controllers in NestJS - I have used dependency injection - which works fine for the api ( REST endpoints ).
The problem is that I have moved away from OOP - so all of my libraries are written in typescript as functions. Not pure functions but better than an OOP approach ( many less bugs! )
My question is - how do I get access to the main winston logger within my libraries that don't have classes.
I am using the library nest-winston.
Have you tried this?
create the logger outside of the application lifecycle, using the createLogger function, and pass it to NestFactory.create (nest-winston docs)
You can have a separate file that creates the logging instance, then import that into your modules/libraries as well as import it into your main.ts
// src/logger/index.ts
import { WinstonModule } from 'nest-winston';
export const myLogger = WinstonModule.createLogger({
// options (same as WinstonModule.forRoot() options)
})
// src/myLib/index.ts
import { myLogger } from '#/logger' // I like using aliases
export const myLib = () => {
// ...
myLogger.log('Yay')
}
// src/main.ts
import { myLogger } from '#/logger'
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: myLogger
});
}
bootstrap();

Move Express middleware to separate modules

I have lots of app.use(...) in my express app, and they get messy and complex and there's config all over the place. So I want to move them to separate modules so they're easier to test.
So I have this Middleware.ts base class for my middleware wrappers:
export default abstract class Middleware {
public run(): express.RequestHandler {
try {
return this.runImpl;
}
catch (err) {
log("Failed to init middleware " + err);
throw err;
}
}
public abstract runImpl(): express.RequestHandler;
}
And a subclass, e.g. StaticMiddleware.ts for serving static files:
export class StaticMiddleware extends Middleware {
public runImp(): express.RequestHandler {
// this is a simple example, but most middleware are more
// complicated, but everything is encapsulated here
return express.static("path");
}
}
export default new StaticMiddleware().run;
Finally the app.ts express app itself, which is very clean:
import staticMiddleware from "./StaticMiddleware";
import fooMiddleware from "./FooMiddleware";
import barMiddleware from "./BarMiddleware";
app.use(staticMiddleware());
app.use(fooMiddleware());
app.use(barMiddleware());
But I get an error at runtime:
TypeError: app.use() requires a middleware function
Note it's possible to change the method signatures above, so I can use this form:
app.use(staticMiddleware);
But that is surprising to a maintainer, and so liable to break. I want the normal signature:
app.use(staticMiddleware());

Mock $root with vue-test-utils and JEST

My component has the following computed code:
textButton() {
const key = this.$root.feature.name === //...
// ...
},
Right now I'm trying desperately to mock "root" in my test, but I just don't know how. Any tips?
Vue test utils provides you with the ability to inject mocks when you mount (or shallow mount) your component.
const $root = 'some test value or jasmine spy'
let wrapper = shallow(ComponentToTest, {
mocks: { $root }
})
That should then be easily testable. Hope that helps
There are two ways to accomplish this with vue-test-utils.
One way, as mentioned above, is using the mocks mounting option.
const wrapper = shallowMount(Foo, {
mocks: {
$root: {
feature: {
name: "Some Val"
}
}
}
})
But in your case, you probably want to use the computed mouting option, which is a bit cleaner than a deep object in mocks.
const wrapper = shallowMount(Foo, {
computed: {
textButton: () => "Some Value"
}
})
Hopefully this helps!
If you are interested I am compiling a collection of simple guides on how to test Vue components here. It's under development, but feel free to ask make an issue if you need help with other related things to testing Vue components.
Solution from https://github.com/vuejs/vue-test-utils/issues/481#issuecomment-423716430:
You can set $root on the vm directly:
wrapper.vm.$root = { loading: true }
wrapper.vm.$forceUpdate()
Or you can pass in a parent component with the parentComponent mounting option. In VTU, the paren will be the $root:
const Parent = {
data() {
return {
loading: "asdas"
}
}
}
const wrapper = shallowMount(TestComponent, {
parentComponent: Parent
})
You may use a Vue Plugin inside the test to inject the mock data into localVue so that your components can access it.
import {createLocalVue, shallow} from '#vue/test-utils';
const localVue = createLocalVue();
localVue.use((Vue) => {
Vue.prototype.feature = {
name: 'Fake news!'
};
});
let wrapper = shallow(Component, {
localVue
});
I had the same issues a while ago and I came to the conclusion that accessing this.$root may be a sign that you have to further improve the way you communicate with components. Consider using the plugin structure to define globally available properties and methods not only inside the test for example. Mixins might be helpful as well.
https://v2.vuejs.org/v2/guide/plugins.html
https://v2.vuejs.org/v2/api/#mixins

Resources