How to import fs on the server side - node.js

I'm trying to send an ssr page containing the contents of a txt file, however I'm stuck on the part of being able to import the fs.
I understand that I need to be on the server side to be able to import the fs, I tried to use this code, without success
// pages/index.js
import fs from 'fs'
export async function getServerSideProps(ctx) {
return {
props: {}
}
}
export default function Home() {
return (
<h1>...</h1>
)
}

You can use import() or require() syntax inside getServerProps.
// pages/index.js
export async function getServerSideProps(ctx) {
const fs = await import('fs');
// or
const fs = require('fs');
return {
props: {},
};
}
export default function Home() {
return <h1>...</h1>;
}

Related

How to mock file system with memfs in NodeJS

I want to test the following function with the memfs package
import fs from 'fs-extra'
import path from 'path'
async function ensureParent(filepath: string) {
const absolutePath = path.resolve(filepath)
const parentDir = path.dirname(absolutePath)
const exists = await fs.pathExists(parentDir)
return exists
}
export default {
ensureParent,
}
To test it with an in-memory file system I have at the root of the project, the following __mocks__/fs.ts file
import { fs } from 'memfs'
export default fs
and the following __mocks__/fs/promises.ts file
import { fs } from 'memfs'
export default fs.promises
Here is the test file file.test.ts
import { describe, it, expect, vi, beforeAll, beforeEach } from 'vitest'
import { vol } from 'memfs'
import f from '../file'
vi.mock('fs')
vi.mock('fs/promises')
const fileSystem = {
'./exists/shiman.txt': 'Hello shiman',
}
beforeAll(() => {
vol.fromJSON(fileSystem, '/tmp')
})
beforeEach(() => {
vol.reset()
})
describe.concurrent('file utils', () => {
it('ensure a directory exists', async () => {
expect(await f.ensureParent('/tmp/exists/shiman.txt')).toBe(true)
})
})
However, the test keeps failing, and I realized that, although the mock is taken into account, the in-memory file system is not because await fs.readdir('/', (err, data) => console.log(err, data)) logs the content of / in my machine.
Have I done something wrong?

Fastify CLI decorators undefined

I'm using fastify-cli for building my server application.
For testing I want to generate some test JWTs. Therefore I want to use the sign method of the fastify-jwt plugin.
If I run the application with fastify start -l info ./src/app.js everything works as expected and I can access the decorators.
But in the testing setup I get an error that the jwt decorator is undefined. It seems that the decorators are not exposed and I just can't find any error. For the tests I use node-tap with this command: tap \"test/**/*.test.js\" --reporter=list
app.js
import { dirname, join } from 'path'
import autoload from '#fastify/autoload'
import { fileURLToPath } from 'url'
import jwt from '#fastify/jwt'
export const options = {
ignoreTrailingSlash: true,
logger: true
}
export default async (fastify, opts) => {
await fastify.register(jwt, {
secret: process.env.JWT_SECRET
})
// autoload plugins and routes
await fastify.register(autoload, {
dir: join(dirname(fileURLToPath(import.meta.url)), 'plugins'),
options: Object.assign({}, opts),
forceESM: true,
})
await fastify.register(autoload, {
dir: join(dirname(fileURLToPath(import.meta.url)), 'routes'),
options: Object.assign({}, opts),
forceESM: true
})
}
helper.js
import { fileURLToPath } from 'url'
import helper from 'fastify-cli/helper.js'
import path from 'path'
// config for testing
export const config = () => {
return {}
}
export const build = async (t) => {
const argv = [
path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'src', 'app.js')
]
const app = await helper.build(argv, config())
t.teardown(app.close.bind(app))
return app
}
root.test.js
import { auth, build } from '../helper.js'
import { test } from 'tap'
test('requests the "/" route', async t => {
t.plan(1)
const app = await build(t)
const token = app.jwt.sign({ ... }) //-> jwt is undefined
const res = await app.inject({
method: 'GET',
url: '/'
})
t.equal(res.statusCode, 200, 'returns a status code of 200')
})
The issue is that your application diagram looks like this:
and when you write const app = await build(t) the app variable points to Root Context, but Your app.js contains the jwt decorator.
To solve it, you need just to wrap you app.js file with the fastify-plugin because it breaks the encapsulation:
import fp from 'fastify-plugin'
export default fp(async (fastify, opts) => { ... })
Note: you can visualize this structure by using fastify-overview (and the fastify-overview-ui plugin together:

how to prevent file upload when body validation fails in nestjs

I have the multipart form to be validated before file upload in nestjs application. the thing is that I don't want the file to be uploaded if validation of body fails.
here is how I wrote the code for.
// User controller method for create user with upload image
#Post()
#UseInterceptors(FileInterceptor('image'))
create(
#Body() userInput: CreateUserDto,
#UploadedFile(
new ParseFilePipe({
validators: [
// some validator here
]
})
) image: Express.Multer.File,
) {
return this.userService.create({ ...userInput, image: image.path });
}
Tried so many ways to turn around this issue, but didn't reach to any solution
Interceptors run before pipes do, so there's no way to make the saving of the file not happen unless you manage that yourself in your service. However, another option could be a custom exception filter that unlinks the file on error so that you don't have to worry about it post-upload
This is how I created the whole filter
import { isArray } from 'lodash';
import {
ExceptionFilter,
Catch,
ArgumentsHost,
BadRequestException,
} from '#nestjs/common';
import { Request, Response } from 'express';
import * as fs from 'fs';
#Catch(BadRequestException)
export class DeleteFileOnErrorFilter implements ExceptionFilter {
catch(exception: BadRequestException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const getFiles = (files: Express.Multer.File[] | unknown | undefined) => {
if (!files) return [];
if (isArray(files)) return files;
return Object.values(files);
};
const filePaths = getFiles(request.files);
for (const file of filePaths) {
fs.unlink(file.path, (err) => {
if (err) {
console.error(err);
return err;
}
});
}
response.status(status).json(exception.getResponse());
}
}

Cannot read property 'resolve' of undefined when using import path from 'path'

When using :
import path from 'path';
path.resolve('/')
I get the title error, but when I use
require('path').resolve('messages.json'))
import { readFile, writeFile } from 'fs/promises';
import { v4 as uuidv4 } from 'uuid';
import path from 'path';
interface MessagesJson {
messages: Array<{ content: string; id: string }>;
}
export class MessagesRepository {
async findOne(id: string): Promise<string> {
return id;
}
async findAll(): Promise<any> {
return null;
}
async create(message: any): Promise<any> {
console.log(' dirname', require('path').resolve('messages.json'));
console.log(' path.resolve', path.resolve('/'));
// console.log(' path.resolve', path.resolve(__dirname, '/src'));
const messages: any = await readFile('src/messages.json', 'utf-8');
const parsedMessages: MessagesJson = JSON.parse(messages);
const newMessage = {
content: message,
id: uuidv4(),
};
await parsedMessages.messages.push(newMessage);
await writeFile('src/messages.json', JSON.stringify(parsedMessages));
return parsedMessages;
}
}
For the problem context, I'm working on a small project with nestjs, any option for path.foo() gets the same error as listed above, is it something related for after the compiling of the code?
Im very lost to where/what doc and/or information should i be reading to be able to understand what is happening.
require on it's own is, by technicality, a named import. This means that in Typescript it needs to be like import * as path from 'path' so that you can make use of path.resolve. Another option would be to deconstruct the import by using import { resolve } from 'path'; and now you can just call resolve() directly.

What is the correct way to dynamically import a class inside of a Firebase Cloud function using typescript?

In a Firebase Cloud Function project...
I have the following typescript file at the root of my src directory right along side of my main index.ts file which imports one dependency and exports a class that includes 2 methods. This file is titled bcrypt.class.ts:
import * as bcrypt from 'bcryptjs';
export default class BcryptTool {
public static hashValue(value: string, rounds: number, callback: (error: Error, hash: string) => void) : void {
bcrypt.hash(value, rounds, (error:any, hash:any) => {
callback(error, hash);
});
}
public static compare(value: string, dbHash: string, callback: (error: string | null, match: boolean | null) => void) {
bcrypt.compare(value, dbHash, (err: Error, match: boolean) => {
if(match) {
callback(null, true);
} else {
callback('Invalid value match', null);
}
});
}
}
In my Firebase Cloud functions index.ts file I import this class and make a call to it's 'compare' method within one of my functions without issue, this works as expected:
'use strict';
const express = require('express');
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true });
const admin = require('firebase-admin');
admin.initializeApp();
const api = express();
import BcryptTool from './bcrypt.class'; // <-- i import the class here
// and use it in a function
api.use(cors);
api.post('/credentials', async (request: any, response: any) => {
BcryptTool.compare(...) // <--- calls to this method succeed without issue
});
The problem
My application includes many functions, but I only need the class noted above in one of them, so in an attempt to optimize cold start time for all my other functions, I attempt to dynamically import this class inside of the function that needs it instead of importing it into the global scope as outlined above. This does not work and I cannot figure out why:
'use strict';
const express = require('express');
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true });
const admin = require('firebase-admin');
admin.initializeApp();
const api = express();
api.use(cors);
api.post('/credentials', async (request: any, response: any) => {
const BcryptTool = await import('./bcrypt.class'); // <-- when i attempt to import here instead
BcryptTool.compare(...) // <--- subsequent calls to this method fail
// Additionally, VS Code hinting displays a warning: Property 'compare' does not exist on type 'typeof import('FULL/PATH/TO/MY/bcrypt.class')'
});
Is my class not written or exported correctly?
Am I not importing the class correctly inside of my cloud function?
The top-level import (import BcryptTool from './bcrypt.class';) will automatically import the default export from the bcrypt.class module. However, when using the import statement as a function (so called "dynamic import"), it will import the module itself, not the default export.
You can see the difference when you would console.log(BcryptTool) both imports:
import BcryptTool from './bcrypt.class' will show { default: { [Function: BcryptTool] hashValue: [Function], compare: [Function] } }
const BcryptTool = await require('bcrypt.class') will show { [Function: BcryptTool] hashValue: [Function], compare: [Function] }
Did you notice the default in the first console.log? That shows you imported the module, not the default.
Now actually the import BcryptTool from './bcrypt.class' syntax is syntactic sugar for doing import { default as BcryptTool } from './bcrypt.class'. If you apply this knowledge on the dynamic import, you could do this:
const BcryptToolModule = await import('./bcrypt.class');
BcryptToolModule.default.compare(...);
Or in a cleaner syntax:
const { default: BcryptTool } = await import('./bcrypt.class');
BcryptTool.compare(...);

Resources