This chapter is about creating server requests, let's start with the listing directory.
No Security
This project is not about security, so we'll not be adding any security layer to our server, we'll just make it work.
So don't use it in production, it's just for learning purposes.
Defining Routes
Let's start by listing the directory, we'll create a new route in our server, and we'll call it /file-manager
and it will accept a path
as query string.
Let's create src/routes.ts
file and add the following code:
// routes.ts
import { Express } from "express";
export default function listRoutes(app: Express) {
//
}
Now let's import it from our src/index.ts
file and call it.
// index.ts
// imported express server
import express, { Express } from "express";
ππ» import listRoutes from "./routes";
// port to run the server
const port = 8001;
// ππ» create express app
const app: Express = express();
listRoutes(app);
// start the Express server
app.listen(port, () => {
console.log(`β‘οΈ[server]: Server is running at http://localhost:${port}`);
});
Now let's define our routes list
import { Express } from "express";
import fileManager from "./controllers/file-manager";
export default function listRoutes(app: Express) {
// Let's define our routes
// list directory nodes
app.get("/file-manager", fileManager.list);
// create a directory node
app.post("/file-manage/directory", fileManager.createDirectory);
}
We created two requests, one for listing the directory and the other for creating a directory.
Now let's create a new controllers directory which will manage our requests, create src/controllers/file-manager
directory and inside it let's create an index file
// src/controllers/file-manager/index.ts
const fileManager = {
list: listDirectory,
createDirectory: createDirectory,
};
export default fileManager;
We just created a simple object that contains our route mapping, ,list
to list our directory and create
to create a directory.
Now let's create a file for each of them, let's start with listDirectory
file.
// src/controllers/file-manager/listDirectory.ts
import { Request, Response } from "express";
/**
*List directories
*/
export default async function listDirectory(request: Request, response: Response) {
//
}
Now let's define the second function createDirectory
// src/controllers/file-manager/createDirectory.ts
import { Request, Response } from "express";
/**
* Create a directory
*/
export default async function createDirectory(request: Request, response: Response) {
//
}
Let's import it now in our src/controllers/file-manager/index.ts
file
// src/controllers/file-manager/index.ts
import createDirectory from "./createDirectory";
import listDirectory from "./listDirectory";
const fileManager = {
list: listDirectory,
createDirectory: createDirectory,
};
export default fileManager;
Express request and response
As you can see above, when we define a function for a route we always receive two parameters, request
and response
, these are the request and response objects from Express as it injects to our function directly when the request route is match with its corresponding function.
Listing Directory
Now let's list our directory, we'll use @mongez/fs
module to list our directory.
The path of the directory will be relative to /data
directory in our root directory, so if we want to list the /data
directory we'll just use /
as the default path.
// src/controllers/file-manager/listDirectory.ts
import { Request, Response } from "express";
/**
*List directories
*/
export default async function listDirectory(
request: Request,
response: Response
) {
// get the directory path from the request query string
// let's set `/` as the default path
const { path = '/' } = request.query;
}
Config file
Before we continue, we need to get the dataDirectory, we defined it in our generator file, let's create a config.ts file and add it there then we can import it in our files.
// src/config.ts
export const root = process.cwd();
export const dataDirectory = root + "/data";
Now we import it in our generator file.
// src/generator.ts
import { faker } from "@faker-js/faker";
import fs from "@mongez/fs";
// import it here
import { dataDirectory } from "./config";
function start() {
make(3, 3, dataDirectory);
}
start();
function makeFiles(maxFilesPerDirectory: number, path: string) {
const filesList: string[] = [];
while (filesList.length < maxFilesPerDirectory) {
const file = faker.system.fileName();
const filePath = path + "/" + file;
if (!filesList.includes(filePath) && !fs.isFile(filePath)) {
filesList.push(filePath);
fs.put(filePath, faker.lorem.paragraphs());
}
}
}
function make(
maxDirectories: number,
maxFilesPerDirectory: number,
path: string
) {
const directoriesList: string[] = [];
while (directoriesList.length < maxDirectories) {
const directory = faker.system.directoryPath().split("/")[1];
const directoryPath = path + "/" + directory;
if (
!directoriesList.includes(directoryPath) &&
!fs.isDirectory(directoryPath)
) {
directoriesList.push(directoryPath);
fs.makeDirectory(directoryPath, 777);
makeFiles(maxFilesPerDirectory, directoryPath);
// to make recursive directories and files, you'll need to stop the script after some time
// and run it again
// make(maxDirectories, maxFilesPerDirectory, directoryPath);
}
}
}
Now let's get the full path of the directory and check if it exists.
// src/controllers/file-manager/listDirectory.ts
import fs from "@mongez/fs";
import { Request, Response } from "express";
import { dataDirectory } from "../../config";
/**
*List directories
*/
export default async function listDirectory(
request: Request,
response: Response
) {
// get the directory path from the request query string
// let's set `/` as the default path
const { path = "/" } = request.query;
const directoryPath = dataDirectory + path; // the full path to the directory
if (!fs.isDirectory(directoryPath)) {
return response.status(404).json({
message: "Directory not found",
});
}
}
Here we got the full path of the given directory, then we checked if the directory does not exist we'll return a 404 response with a message to indicate directory is not found.
Paths utility
As we can see we added full directory path as follows:
const directoryPath = dataDirectory + path;
Let's make a function to handle it properly.
Now let's create a utility file to handle our paths, let's create src/utils/paths.ts
file
// src/utils/paths.ts
import path from "path";
import { dataDirectory } from "../config";
export function dataPath(relativePath: string): string {
return path.resolve(dataDirectory, relativePath);
}
Here we created a method to get the full path of the given relative path, we used path.resolve
to resolve the full path to generate it properly.
Let's use it now in our listDirectory
function.
// src/controllers/file-manager/listDirectory.ts
import fs from "@mongez/fs";
import { Request, Response } from "express";
import { dataPath } from "../../utils/paths";
/**
*List directories
*/
export default async function listDirectory(
request: Request,
response: Response
) {
// get the directory path from the request query string
// let's set `/` as the default path
const { path = "/" } = request.query;
const directoryPath = dataPath(path); // the full path to the directory
if (!fs.isDirectory(directoryPath)) {
return response.status(404).json({
message: "Directory not found",
});
}
}
Relative Paths
As we can see our paths have ../../
which as you know really hate, so let's install link-module-alias
yarn add link-module-alias -D
Open now package.json
and update it to look like this
{
"name": "backend",
"version": "1.0.0",
"main": "src/index.ts",
"license": "MIT",
"author": {
"name": "Hasan Zohdy",
"email": "hassanzohdy@gmail.com"
},
"scripts": {
// ππ» add this
"postinstall": "npx link-module-alias",
"generate": "npx ts-node ./src/generator.ts",
"start": "npx ts-node ./src/index.ts"
},
// ππ» and this
"_moduleAliases": {
"app": "./src"
},
"dependencies": {
"@faker-js/faker": "^7.5.0",
"@mongez/fs": "^1.0.3",
"express": "^4.18.1"
},
"devDependencies": {
"@types/express": "^4.17.14",
"@types/node": "^18.7.18",
"link-module-alias": "^1.2.0",
"ts-node": "^10.9.1",
"tslib": "^2.4.0",
"typescript": "^4.8.3"
}
}
Final step is to run command postinstall
to link the aliases.
yarn postinstall
If you see this, then everything is ok
Tip Why we named the command
postinstall
? because this is a special command that will be executed after installing the dependencies, so we don't need to run it manually.
Now let's update or listDirectory function to use the new relative path.
// src/controllers/file-manager/listDirectory.ts
import fs from "@mongez/fs";
ππ» import { dataPath } from "app/utils/paths";
import { Request, Response } from "express";
/**
*List directories
*/
export default async function listDirectory(
request: Request,
response: Response
) {
// get the directory path from the request query string
// let's set `/` as the default path
const { path = "/" } = request.query;
const directoryPath = dataPath(path); // the full path to the directory
if (!fs.isDirectory(directoryPath)) {
return response.status(404).json({
message: "Directory not found",
});
}
}
If you get an error from typescript that the function can not accept the path, we can cast it to string
import fs from "@mongez/fs";
import { dataPath } from "app/utils/paths";
import { Request, Response } from "express";
/**
*List directories
*/
export default async function listDirectory(
request: Request,
response: Response
) {
// get the directory path from the request query string
// ππ» let's set `/` as the default path
const path = (request.query.path as string) || "/";
const directoryPath = dataPath(path); // the full path to the directory
if (!fs.isDirectory(directoryPath)) {
return response.status(404).json({
message: "Directory not found",
});
}
}
Now let's list our directories inside the root path of data directory.
// src/controllers/file-manager/listDirectory.ts
...
// get the directory content
const children = fs.list(directoryPath);
return response.json({
node: {
path,
name: path.split("/").pop(),
children,
},
});
Now let's start the server again and test it.
Navigate to our browser to http://localhost:8001/file-manager?path=/
and you'll see the following response:
The output will be like this
Why? because we didn't resolve the paths right, go to dataPath
function and try to log the output of it.
It will be /
!
Let's fix it by making sure the relative path start with ./
So we need to remove any /
and add ./
if it doesn't start with it.
Install @mongez/reinforcements
so we can use some good utilities from it.
yan add @mongez/reinforcements
Now let's modify the dataPath
function to look like this
// src/utils/paths.ts
import { ltrim } from "@mongez/reinforcements";
import path from "path";
import { dataDirectory } from "../config";
export function dataPath(relativePath: string): string {
relativePath = ltrim(relativePath, "/");
return path.resolve(dataDirectory, relativePath);
}
ltrim removes the given string from the beginning of the string, so we will remove
/
to make sure the path is properly set.
Now when we restart the server, we can see results are coming properly.
In our next chapter, we'll create our node properly and distinguish between children files and directories and create createDirectory
request as well.
Article Repository
You can see chapter files in Github Repository
Don't forget the
main
branch has the latest updated code.
Tell me where you are now
If you're following up with me this series, tell me where are you now and what you're struggling with, i'll try to help you as much as i can.
Salam.
Top comments (0)