fs.readdir
returns an array of files within a given directory path
const fs = require('fs').promises;
try {
const paths = await fs.readdir("data");
console.log("data directory contents: ");
console.log(paths);
} catch (err) {
console.error("Error reading directory");
}
JS (async/await version)
Example code: directory-reading.js
glob
glob(pattern, [options], (err, matches) => { ... });
JS (syntax)
glob("*.png", (err, matches) => {
if (err) {
console.error("There was an error: " + err);
} else {
console.log(".png images in current directory:", matches);
}
});
JS
Check out some useful documentation of more features here
Examples: glob-examples.js
What's the difference between Glob and Readdir?
Since glob takes a callback as its last argument, we can use utils.promisify
to
define a promise-returning version.
const utils = require('utils');
const globPromise = utils.promisify(glob);
async function globAsync() {
try {
let matches = await globPromise("data/*");
console.log(".png images in current directory:", matches);
} catch(err) {
console.error("There was an error: " + err);
}
}
JS
Often, web services don't rely on hard-coding data in files
For example, our menu web service returns JSON, but what if we want to support different JSON responses?
{
"categories" : {
"Drinks" : [
{
"name" : "Classic coffee",
"description" : "The classic.",
"image" : "coffee.png",
"in-stock" : true
}, ...
],
"Foods" : [
...
]
}
We might want to keep track of item quantity for example, and uses that to determine the in-stock key
Let's take a closer look at the new directory structure, and get a bit more practice with glob
The cafe web service now holds its data in a directory structure to help process the results
cse154-cafe/
app.js
categories/
Drinks/
bubble-tea/
info.txt
purchase-history.txt
classic-coffee/
info.txt
purchase-history.txt
...
Foods/
...
public/
fetch-menu.js
admin.js
img/
stock-img/
...
Contents (abbreviated)
let categoryPaths = await globPromise("categories/*");
// ["categories/Foods", "categories/Drinks"]
let publicFolders = await globPromise("public/*/");
// ["public/img/"]
let drinksInfo =
await globPromise("categories/Drinks/*/info.txt");
// ["categories/Drinks/bubble-tea/inf.txt",
// "categories/Drinks/classic-coffee/info.txt",
// "categories/Drinks/the-sippy/info.txt"]
// ** recursively searches within the current directory
// and all subdirectories
let allJS = await globPromise("**/*.js");
// ["app.js", "public/fetch-menu.js", "public/admin.js"]
// can use [patt1|patt2|...] to match patt1 or patt2 or ...
let allImages = await globPromise("**/*.[png|jpg]");
JS
Like many other fs
functions, there is an optional "options" parameter to the glob
function to support
various useful options
To use, just pass an object as the second argument with the option(s) you want as keys.
// the nodir option ignores directories
let dataFiles = await globPromise("data/*", { "nodir" : true });
// the ignore option takes patterns to ignore
let noGifs = await globPromise("public/img/*", { "ignore" : ['*png'] });
Use 400 (Invalid Requests) for client-specific errors.
Use 500 (Server error) status codes for errors that are independent of any client input.
fs
or glob
that are not related to any request parameters
With GET endpoints, we've used req.params
and req.query
to get endpoint parameters passed
in the request URL.
But remember that POST requests send parameters in the Request body, not in the URL.
app.post("/contact", (req, res) => {
let name = req.params.name; // this doesn't work!
...
});
JS
What is a disadvantage of sending parameters in a URL?
So, how can we get POST parameters sent by a client?
POST requests can be sent with different data types:
application/x-www-form-urlencoded
application/json
multipart/form-data
In Express, we have to use middleware to extract the POST parameters from the req.body
. For the first two, there is built-in middleware - we don't need middleware for text/plain.
With forms and fetch, we use the FormData
object to send POST parameters, which is always
sent as multipart/form-data
.
There is no built-in middleware to access the req.body
params for multipart content.
Another module!
multer
ModuleA module for extracting POST parameter values sent through multipart POST requests like those sent with FormData
Has a lot of functionality to support file uploading, but we will just use it to access
the body of a POST request sent through FormData, which we can't get with just
req.body
.
To use, we'll need to set an option to ignore upload features with multer().none()
const multer = require("multer");
app.use(multer().none());
JS
Remember to run npm install multer
in any project that uses it.
We often don't want to make assumptions about what method a client uses to POST data. It's best to support all three with the appropriate middleware.
// other required modules ...
const multer = require("multer");
// for application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true })) // built-in middleware
// for application/json
app.use(express.json()); // built-in middleware
// for multipart/form-data (required with FormData)
app.use(multer().none()); // requires the "multer" module
...
app.post("/contact", (req, res) => {
let name = req.body.name;
let email = req.body.email;
let message = req.body.message;
let timestamp = new Date.toUTCString();
// validate parameters, then update message.json file with new data
...
});
JS
app.post
instead of app.get
req.body.paramname
instead of req.params.paramname
/req.query.paramname
multer
(non-core) module with the rest of your modulesfetch
and FormData
in client-side JS (similar to HW3) - remember you can't test POST requests in the URL!
At minimum, all route functions must be documented (1-2 sentences) and your overall file header should include a summary of all endpoints (with their response formats/possible errors)
If you have API documentation, it can be much easier to refer to in your header comments
There are provided templates (APIDOC.md), but you are free to modify it based on what you like most.
If you would like to use an API documentation tool generator (such as POSTMan or Swagger), you are free to do so, but must receive permission from the instructor to receive credit in your submission in place of APIDOC.md.
We'll get more practice in section tomorrow!