Building REST APIs is a common task for modern web applications and services. Whether you are creating an internal API for an existing application, building a public API for third party access, or powering a mobile/web application, being able to accept and respond to HTTP requests is a fundamental skill. While many REST API frameworks exist to streamline development, it is still important to understand how to build the core functionality from scratch using minimal dependencies.
In this article we will build a basic REST API using Node.js and Express that is capable of writing files to the local file system based on incoming HTTP requests. Although simple, this demonstrates how to handle common REST actions like GET, POST, PUT, and DELETE as well as perform persistence beyond just database operations. Being able to write physical files locally using a REST API opens up many possibilities for applications that need to generate or process file-based data and assets.
Let’s start by setting up the basic project structure and dependencies:
Copy
├── package.json
├── app.js
└── routes
└── files.js
Our package.json will contain:
json
Copy
{
“name”: “rest-file-api”,
“version”: “1.0.0”,
“description”: “REST API for writing files”,
“main”: “app.js”,
“scripts”: {
“start”: “node app.js”
},
“dependencies”: {
“body-parser”: “^1.19.0”,
“express”: “^4.17.1”
}
}
This sets up our project name, version, main entry point, start script, and declares dependencies on Body Parser and Express.
Next, in app.js we will initialize Express and load our route handlers:
js
Copy
const express = require(‘express’);
const bodyParser = require(‘body-parser’);
const app = express();
app.use(bodyParser.json());
app.use(‘/files’, require(‘./routes/files’));
app.listen(3000, () => {
console.log(‘API running on port 3000!’);
});
We initialize Express, use the bodyParser middleware to parse JSON request bodies, and load the “/files” route handler from our separate routes/files.js module.
Now in routes/files.js we can define the route handlers:
js
Copy
const express = require(‘express’);
const router = express.Router();
const fs = require(‘fs’);
router.get(‘/’, (req, res) => {
res.json({message: ‘Files route’});
});
router.post(‘/’, (req, res) => {
const filename = req.body.filename;
const filecontents = req.body.contents;
fs.writeFile(`files/${filename}`, filecontents, err => {
if (err) {
res.status(500).send(err);
} else {
res.status(201).send({message: ‘File created’});
}
});
});
// additional routes
module.exports = router;
We require Express and the FileSystem module, initialize a router, and define a GET and POST handler.
The GET just responds with a simple message. The POST handler takes the filename and contents from the request body, writes it to the filesystem with fs.writeFile() in a files subdirectory, and returns a success or error response accordingly.
Now when we make a POST request to /files with a filename and contents property, it will write an actual file to the local filesystem!
Let’s add additional CRUD routes for PUT, GET, and DELETE:
js
Copy
// GET single file
router.get(‘/:filename’, (req, res) => {
const filename = req.params.filename;
fs.readFile(`files/${filename}`, (err, data) => {
if (err) {
res.status(404).send({message: ‘File not found’});
} else {
res.send(data);
}
});
});
// Update file
router.put(‘/:filename’, (req, res) => {
const filename = req.params.filename;
const filecontents = req.body.contents;
fs.writeFile(`files/${filename}`, filecontents, err => {
if (err) {
res.status(500).send(err);
} else {
res.send({message: ‘File updated’});
}
});
});
// Delete file
router.delete(‘/:filename’, (req, res) => {
const filename = req.params.filename;
fs.unlink(`files/${filename}`, err => {
if (err) {
res.status(404).send({message: ‘File not found’});
} else {
res.status(204).send();
}
});
});
Now we have full CRUD capabilities – read, create, update and delete files via REST.
To complete the implementation we should:
Add error handling for file operations
Validate request data
Add pagination/filtering for GET index
Implement authentication
Add database/cache for metadata
Containerize API for production deployment
But this covers the basic logic of building a REST API endpoint capable of writing files to the local filesystem on the server. Some key takeaways:
Express is great for building REST APIs
Separate route handlers into modular files
Node’s FileSystem module provides file I/O
Implement common REST verbs (GET, POST, PUT, DELETE)
Respond with appropriate status codes
Handle errors consistently
Being able to persist data beyond just a database unlocks many possibilities. File-based REST APIs open up opportunities like processing user uploads/downloads, generating dynamic assets, logging/reporting, and more. With this basic example you have the foundation to build out robust production-ready file APIs.
