Checking If a File Exists in Node.js: A Developer’s Guide

Mateen Kiani

Mateen Kiani

Published on Sun Jun 22 2025·4 min read

checking-if-a-file-exists-in-node.js:-a-developer’s-guide

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:

MethodTypeBlocking?Example
fs.existsSyncSynchronousYesfs.existsSync(path)
fs.access (callback)AsynchronousNofs.access(path, cb)
fs.promises.accessPromiseNoawait 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.

  1. Receive userId from request parameters.
  2. Build the file path: const filePath = ./images/${userId}.png.
  3. Use fs.promises.access in an async handler.
  4. If it exists, set res.sendFile(filePath).
  5. 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.


Mateen Kiani
Mateen Kiani
kiani.mateen012@gmail.com
I am a passionate Full stack developer with around 3 years of experience in MERN stack development and 1 year experience in blockchain application development. I have completed several projects in MERN stack, Nextjs and blockchain, including some NFT marketplaces. I have vast experience in Node js, Express, React and Redux.