DEV Community

Masui Masanori
Masui Masanori

Posted on

[TypeScript][Express] Search and load local files

Intro

This time, I will try searching local files and returning them for getting requests.

Environemtns

  • Node.js ver.17.0.1
  • TypeScript ver.4.4.4
  • Express ver.4.17.1
  • ts-node ver.10.4.0

Directories & files

I want to get files from these directories.

sample_files
    L type1
        L subFolder
            L こんにちは.txt
        L webrtc_image.pptx
    L type2
        L sub folder
            L こんにちは.txt
    L Hello world!.txt
Enter fullscreen mode Exit fullscreen mode

Get directory and file informations

I can get directory and file informations by "fs.promises.readdir".

fileLoader.ts

import fs from "fs";

async function getFilePath(): Promise<void> {
    const directory = "C:/Users/example/OneDrive/Documents/workspace/express-sample/sample_files";    

    // get directory or file informations as fs.Dirent[]
    const paths = await fs.promises.readdir(directory, { withFileTypes: true});

    // get only file names as string[]
    // const paths = await fs.promises.readdir(directory);

    for(const p of paths) {
        console.log(`Path ${p.name} Dir:${p.isDirectory()}`);  
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

Path Hello world!.txt Dir:false
Path type1 Dir:true
Path type2 Dir:true
Enter fullscreen mode Exit fullscreen mode

Search sub directories

"fs.promises.readdir" can't get from sub directories.
So I have to do by my self.

fileLoader.ts

import fs from "fs";
import path from "path/posix";

const rootDirectory = "C:/Users/example/OneDrive/Documents/workspace/express-sample/sample_files";    
export async function loadFile(): Promise<void> {
    const gotPath = await getFilePath(rootDirectory);    
    console.log(gotPath);
}
async function getFilePath(directory: string): Promise<string|null> {
    // get directory or file informations as fs.Dirent[]
    const paths = await fs.promises.readdir(directory, { withFileTypes: true});
    for(const p of paths) {
        if(p.isDirectory()) {
            console.log(`Folder ${p.name} Dir:${directory}`);
            await getFilePath(path.join(directory, p.name));
        } else {
            console.log(`File ${p.name} Dir:${directory}`);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Results

File Hello world!.txt Dir:C:/Users/example/OneDrive/Documents/workspace/express-sample/sample_files
Folder type1 Dir:C:/Users/example/OneDrive/Documents/workspace/express-sample/sample_files
Folder subFolder Dir:C:/Users/example/OneDrive/Documents/workspace/express-sample/sample_files/type1
File こんにちは.txt Dir:C:/Users/example/OneDrive/Documents/workspace/express-sample/sample_files/type1/subFolder
File webrtc_image.pptx Dir:C:/Users/example/OneDrive/Documents/workspace/express-sample/sample_files/type1
Folder type2 Dir:C:/Users/example/OneDrive/Documents/workspace/express-sample/sample_files
Folder sub folder Dir:C:/Users/example/OneDrive/Documents/workspace/express-sample/sample_files/type2
File こんにちは.txt Dir:C:/Users/example/OneDrive/Documents/workspace/express-sample/sample_files/type2/sub folder
Enter fullscreen mode Exit fullscreen mode

And search specified files by name.

fileLoader.ts

...
export async function loadFile(): Promise<void> {
    const gotPath = await getFilePath(rootDirectory, "webrtc_image.pptx");
    console.log(gotPath);
}
async function getFilePath(directory: string, fileName: string): Promise<string|null> {
    // get directory or file informations as fs.Dirent[]
    const paths = await fs.promises.readdir(directory, { withFileTypes: true});
    for(const p of paths) {
        if(p.isDirectory()) {
            const filePath = await getFilePath(path.join(directory, p.name), fileName);
            if(filePath != null) {
                return filePath;
            }
        } else {
            if(p.name === fileName) {
                return path.join(directory, p.name);
            }
        }
    }
    return null;
}
Enter fullscreen mode Exit fullscreen mode

Results

C:/Users/example/OneDrive/Documents/workspace/express-sample/sample_files/type1/webrtc_image.pptx
Enter fullscreen mode Exit fullscreen mode

Load files or create error messages

Next, I will get files from the gotten file paths and return the data.
But if I won't be able to find the files, I want to return error messages as JSON files.

file.type.ts

export type DownloadFile = {
    name: string,
    mimeType: "application/json"|"application/vnd.openxmlformats-officedocument.presentationml.presentation"|"text/plain",
    fileData: Buffer, 
};
Enter fullscreen mode Exit fullscreen mode

fileLoader.ts

import fs from "fs";
import path from "path/posix";
import { DownloadFile } from "./file.type";

...  
export async function loadFile(fileName: string): Promise<DownloadFile> {
    const gotPath = await getFilePath(rootDirectory, fileName);    
    if(gotPath == null) {
        return getFailedMessage(`File: ${fileName} was not found`);
    }
    const extension = getMimeType(path.extname(gotPath));
    if(extension == null) {
        return getFailedMessage(`Unknown type File: ${fileName}`);
    }
    const fileData = await fs.promises.readFile(gotPath);
    return {
        name: fileName,
        mimeType: extension,
        fileData,
    };
}
...
function getFailedMessage(errorMessage: string): DownloadFile {
    const failedResult = {
        succeeded: false,
        errorMessage
    };
    const fileData = Buffer.from(JSON.stringify(failedResult));
    return {
        name: "failedResult.json",
        mimeType: "application/json",
        fileData,
    };
}
function getMimeType(value: string): "application/json"|
        "application/vnd.openxmlformats-officedocument.presentationml.presentation"|
        "text/plain"|
        null {
    switch(value) {
        case ".json":
            return "application/json";
        case ".pptx":
            return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
        case ".txt":
            return "text/plain";
    }
    return null;
}
Enter fullscreen mode Exit fullscreen mode

Return file data

I will set loaded or generated file data as response value.

index.ts

import express from "express";
import { loadFile } from "./files/fileLoader";

const port = 3098;
const app = express();

app.get('/files', async (req, res) => {
    // TODO: get file names from URL paramters
    const result = await loadFile("webrtc_image.pptx");
    res.setHeader("File-Name", result.name);
    res.setHeader("Content-Type", result.mimeType);

    res.write(result.fileData);
    res.end();
});
app.listen(port, () => {
    console.log(`Example app listening at http://localhost:${port}`)
});
Enter fullscreen mode Exit fullscreen mode

Set multi byte text into Response Headers

One problem is I can't set multi byte text into Response Headers.
Or I will get an exception.

TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["File-Name"]
    at ServerResponse.setHeader (node:_http_outgoing:579:3)
    at ServerResponse.header (C:\Users\example\OneDrive\Documents\workspace\express-sample\node_modules\express\lib\response.js:771:10)
    at C:\Users\example\OneDrive\Documents\workspace\express-sample\index.ts:15:9
    at step (C:\Users\example\OneDrive\Documents\workspace\express-sample\index.ts:33:23)
    at Object.next (C:\Users\example\OneDrive\Documents\workspace\express-sample\index.ts:14:53)
    at fulfilled (C:\Users\example\OneDrive\Documents\workspace\express-sample\index.ts:5:58) {
  code: 'ERR_INVALID_CHAR'
}
Enter fullscreen mode Exit fullscreen mode

So I encode the file name.

index.ts

...
app.get('/files', async (req, res) => {
    // TODO: get file names from URL paramters
    const result = await loadFile("こんにちは.txt");
    res.setHeader("File-Name", encodeURI(result.name));
    res.setHeader("Content-Type", result.mimeType);

    res.write(result.fileData);
    res.end();
});
...
Enter fullscreen mode Exit fullscreen mode

Discussion (0)