This content originally appeared on DEV Community and was authored by Mateen Kiani
We all know that working with the file system is a core part of many Node.js applications, from simple scripts to complex servers. Yet developers often overlook the nuances of checking whether a file exists before accessing it. What’s the best way to perform this check without introducing bugs or blocking the event loop?
By understanding the differences between synchronous, callback-based, and promise-based approaches, you can choose the right method for your project. Let’s explore the options so you can make informed decisions, avoid surprises under load, and keep your code clean and reliable.
Using fs.existsSync for Quick Checks
The simplest way to see if a file exists is with the synchronous method fs.existsSync
. It returns a boolean and halts execution until it finishes. For small scripts or startup checks, this can be fine. But in a busy server, it can block other requests.
const fs = require('fs')
const path = './data/config.json'
if (fs.existsSync(path)) {
console.log('Config file found')
} else {
console.warn('Config file missing')
}
Practical tips:
- Only use
existsSync
at startup or in CLI tools. - Wrap calls in
try/catch
if you expect permission errors. - Remember blocking I/O can hurt performance under load.
By limiting sync checks to non-critical paths, you avoid clogging your server’s event loop.
Async Check with fs.access
For non-blocking code, Node.js offers fs.access
, which takes a callback. It uses flags such as fs.constants.F_OK
to test for existence without reading data. This is ideal in request handlers or background jobs.
const fs = require('fs')
const file = './logs/app.log'
fs.access(file, fs.constants.F_OK, err => {
if (err) {
console.error('Log file does not exist')
} else {
console.log('Log file is present')
}
})
Tip: Always check the
err.code
property for granular control (e.g., 'ENOENT').
Use cases:
- Validating upload targets.
- Ensuring temp files exist before processing.
- Scheduling checks with cron-like jobs (see cron job setup).
Promise-based fs.promises.access
Modern Node.js supports promises in the fs.promises
API. This fits nicely with async/await
, making code cleaner.
const fs = require('fs').promises
async function checkFile(path) {
try {
await fs.access(path)
console.log('File exists')
} catch {
console.log('File not found')
}
}
checkFile('./data/users.json')
Advantages:
- Flows naturally with async functions.
- Avoids deep callback nesting.
- You can chain with other promise-based tasks, such as saving data (see saving JSON data).
Comparing Methods Side by Side
Here’s a quick comparison to help you choose:
Method | Type | Blocking? | Example |
---|---|---|---|
fs.existsSync | Synchronous | Yes | fs.existsSync(path) |
fs.access (callback) | Asynchronous | No | fs.access(path, cb) |
fs.promises.access | Promise | No | await fs.promises.access(path) |
If you need to list files first, check this guide: list files in a directory.
Common Pitfalls and Best Practices
Even with the right method, mistakes can sneak in. Keep these tips in mind:
- Don’t rely on a single
exists
check before reading; the file might change immediately after. - Handle permission errors distinctly from “not found.”
- Avoid deep nesting; prefer promise or async/await patterns.
- Cache results if you check the same file often.
- Always sanitize paths to prevent directory traversal.
Best practice: Combine
try/catch
with clear error messages to simplify debugging.
Real-world Example in an API Route
Imagine an API that allows clients to fetch profile images. You need to check if the image file exists before streaming it.
- Receive
userId
from request parameters. - Build the file path:
const filePath = ./images/${userId}.png
. - Use
fs.promises.access
in anasync
handler. - If it exists, set
res.sendFile(filePath)
. - On error, respond with
404
and a friendly message.
const fs = require('fs').promises
app.get('/api/avatar/:id', async (req, res) => {
const img = `./images/${req.params.id}.png`
try {
await fs.access(img)
res.sendFile(img, { root: __dirname })
} catch {
res.status(404).json({ error: 'Avatar not found' })
}
})
This approach keeps your API fast, non-blocking, and user-friendly.
Conclusion
Checking if a file exists may seem trivial, but choosing the right method can have a big impact on performance and reliability. Use fs.existsSync
for quick startup checks, fs.access
callbacks for non-blocking code, and fs.promises.access
when you want clean async/await
flows. Remember the pitfalls—race conditions, permission errors, and blocking calls—and apply best practices like clear error handling and caching.
Armed with these patterns, you can confidently guard your file operations and build robust Node.js applications.
Meta description: Check if a file exists in Node.js using fs.existsSync, fs.access, or fs.promises.access with code examples and best practices.
This content originally appeared on DEV Community and was authored by Mateen Kiani

Mateen Kiani | Sciencx (2025-06-26T19:18:03+00:00) Checking If a File Exists in Node.js: A Developer’s Guide. Retrieved from https://www.scien.cx/2025/06/26/checking-if-a-file-exists-in-node-js-a-developers-guide/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.