Get file information in Deno
Introduction
A lot of web apps or REST APIs work with files present on the disk. For example — static content server, dynamic file server, file uploader, file proxy, etc. These apps or APIs may need to get information about the local files like:
- File exists or not
- Size of the file
- Last update of the file
- etc.
We aren’t referring to reading/writing/processing of the file. That is already covered in the article here, here, & here. We’re referring to getting information about the file (again, not the content of the file).
An example: The size of a file is useful in setting content-length if we’re not reading the entire file in memory.
In this article, we’ll go over the two very useful functions that can provided information about a file or a directory. One of these functions is provided by Deno core & the other one is provided by the standard library.
Exists
The first function is: exists/existsSync. This checks if a file exists. It’s a simple, but very useful function. A regular file server would always check if the file exists before opening & serving it. The next function stat/lstat can also be used in place of exists, but exists is much simpler.
To use exists function, it needs to be imported from the standard library’s fs module:
import {exists, existsSync} from "https://deno.land/std/fs/mod.ts";
The inputs & outputs are:
- Input: The only input to the exists function is the file path
- Output: The output of the exists function is boolean (true if file exists, false otherwise).
async function exists(filePath: string): Promise<boolean>
function existsSync(filePath: string): boolean
The exists function internally uses some unstable APIs, therefore unstable needs to be passed to the deno process.
Here are some simple examples:
import {exists} from "https://deno.land/std/fs/mod.ts";await exists('./abcd'); //false as abcd doesn't exists
await exists('./app.ts'); //true as app.ts exists
await exists('/var/tmp'); //true as /var/tmp exists
It’s not that the exists function never throws error. It does throw if there is error in getting details of the file, except for NotFound error that gets converted to false.
stat/lstat
The second useful function is stat/lstat. There is a minor difference between them:
- If the file is a symlink, lstat would not follow it
- If the file is a symlink, stat would always follow symlink
Otherwise, both the functions work the same.
These functions are part of the core runtime, therefore no imports are required.
function stat(path: string | URL): Promise<FileInfo>
function statSync(path: string | URL): FileInfo;
The inputs and output are:
- Input: The only input is the file path
- Output: The output is a FileInfo object
The FileInfo object contains a lot of information about the file. We’ll go over some useful attributes:
- isFile: True if it’s a file, false otherwise
- isDirectory: True if it’s a directory, false otherwise
- isSymlink: True if it’s a symbolic link, false otherwise
- size: The size of file
- mtime, atime, birthtime: The last modification, access & creation time
The stat function throws error if the given path doesn’t exist:
await Deno.stat('./abcd');
//NotFound: No such file or directory (os error 2)
Therefore, it’s good to use exists before calling stat.
The stat function gives the following info for a regular file:
await Deno.stat('./readings100M.txt');
//{
isFile: true,
isDirectory: false,
isSymlink: false,
size: 104857600,
mtime: 2021-06-07T03:06:28.242Z,
atime: 2021-06-17T05:26:29.864Z,
birthtime: 2021-04-07T01:26:28.170Z
}
The stat function gives the following info for a directory:
await Deno.stat('/var/tmp');
//{
isFile: false,
isDirectory: true,
isSymlink: false,
size: 416,
mtime: 2021-06-17T06:19:25.516Z,
atime: 2021-06-17T22:08:10.736Z,
birthtime: 2020-01-01T08:00:00.000Z
}
The stat function gives the following info for a symbolic link (on Mac, /var points to /private/var). As /var is a symbolic link, the stat function follows it and gives info for /private/var.
await Deno.stat('/var');
//{
isFile: false,
isDirectory: true,
isSymlink: false,
size: 992,
mtime: 2021-05-22T22:24:13.049Z,
atime: 2021-05-23T18:51:40.871Z,
birthtime: 2020-01-01T08:00:00.000Z
}
The lstat function gives the following info for the same symbolic link. This time it gives the information about the symbolic link itself, instead of following it.
await Deno.lstat('/var');
//{
isFile: false,
isDirectory: false,
isSymlink: true,
size: 11,
mtime: 2020-01-01T08:00:00.000Z,
atime: 2020-01-01T08:00:00.000Z,
birthtime: 2020-01-01T08:00:00.000Z
}
Example file server
Let’s see one use case of a file server. The file server opens the requested file and then serves it directly. The file isn’t read in its entirety into memory. This would have a memory impact for large size files. When a file is served directly (i.e. as a stream), content-length doesn’t get set. This is where stat command comes into the picture. Before opening the file, stat command would be used to get the size. The content-type would be set in response headers before streaming the file.
Here is the complete code of a simple file server.
import {exists} from "https://deno.land/std/fs/mod.ts";
import {readableStreamFromReader as toStream} from "https://deno.land/std/io/mod.ts";const port=5000, contentDir='/var/tmp';
const listener = Deno.listen({port});for await(const conn of listener)
handleNewConnection(conn);
async function handleNewConnection(conn: Deno.Conn) {
for await(const req of Deno.serveHttp(conn))
await handleRequest(req.request, req.respondWith);
}async function handleRequest(req:Request, resp:any) {
const fileName=new URL(req.url).searchParams.get('filename');
if(!fileName)
return resp(new Response(undefined, {status: 400}));
const filePath=`${contentDir}/${fileName}`;
if(!await exists(filePath))
return resp(new Response(undefined, {status: 404}));
const fileSize=(await Deno.stat(filePath)).size;
const r=await Deno.open(filePath);
resp(new Response(toStream(r), {
headers: new Headers({'content-length': fileSize.toString()})
}));
await r.close();
}
Both the functions i.e. exists and stat are useful in this use case. Here is the output of some sample runs:
curl http://localhost:5000?filename=dummy.jpg -o /dev/null
< HTTP/1.1 404 Not Foundcurl http://localhost:5000?filename=helloFile.txt
< HTTP/1.1 200 OK
< content-length: 13
< date: Fri, 18 Jun 2021 18:51:35 GMT
<
Hello worldcurl http://localhost:5000?filename=readings5G.txt -s -o /dev/null
< HTTP/1.1 200 OK
< content-length: 5000000000
< date: Fri, 18 Jun 2021 18:52:41 GMT
<
This story is a part of the exclusive medium publication on Deno: Deno World.