Jak sprawdzać, czy plik istnieje w Node.js?

Bruno Krawczyk 4 września 2026
Otwórz dedykowane narzędzia deweloperskie dla Node, aby sprawdzić, czy plik istnieje.

Spis treści

W aplikacji Node.js szybko pojawia się potrzeba sprawdzenia, czy wskazany plik istnieje, zanim zostanie odczytany, nadpisany albo zwrócony przez endpoint. Najprostsze rozwiązanie to fs.existsSync(), ale w aplikacji webowej często lepiej użyć operacji asynchronicznej albo od razu spróbować odczytu i obsłużyć błąd. Pokażę, czym różnią się te podejścia, kiedy ich używać oraz jak uniknąć problemów ze ścieżkami i blokowaniem serwera.

Najważniejsze informacje o sprawdzaniu plików w Node.js

  • fs.existsSync() zwraca wartość boolean i dobrze sprawdza się w krótkich skryptach.
  • fs.promises.access() pozwala asynchronicznie sprawdzić dostępność pliku.
  • fs.exists() jest przestarzałe i nie powinno być używane w nowych projektach.
  • Przed późniejszym odczytem pliku lepiej wykonać odczyt od razu i obsłużyć błąd ENOENT.
  • Operacje synchroniczne w obsłudze żądań HTTP mogą blokować event loop.

Najprostsze sprawdzenie przez existsSync

Jeżeli potrzebuję tylko odpowiedzi „jest” albo „nie ma”, najkrótszym rozwiązaniem jest funkcja existsSync() z wbudowanego modułu `node:fs`. Zwraca ona `true`, gdy podana ścieżka istnieje, oraz `false`, gdy nie istnieje.

import { existsSync } from 'node:fs';

const filePath = './data/config.json';

if (existsSync(filePath)) {
  console.log('Plik istnieje');
} else {
  console.log('Pliku nie znaleziono');
}

Ta metoda sprawdza zarówno pliki, jak i katalogi. Jeśli muszę potwierdzić, że ścieżka wskazuje konkretnie na plik, samo `existsSync()` nie wystarczy. Wtedy używam `statSync()` i metody isFile().

import { statSync } from 'node:fs';

const path = './data/config.json';

if (statSync(path, { throwIfNoEntry: false })?.isFile()) {
  console.log('Ścieżka wskazuje na plik');
} else {
  console.log('Plik nie istnieje albo ścieżka prowadzi do katalogu');
}

W praktyce korzystam z `existsSync()` głównie w skryptach uruchamianych lokalnie, podczas konfiguracji aplikacji albo przy ładowaniu ustawień podczas startu procesu. Nie wybieram go bez namysłu w kodzie obsługującym każde żądanie HTTP, ponieważ synchroniczna operacja zatrzymuje wykonywanie innych zadań przez event loop.

Asynchroniczne sprawdzanie w aplikacji webowej

Serwer Node.js najlepiej wykorzystuje swoje możliwości wtedy, gdy operacje wejścia i wyjścia nie blokują wątku wykonującego kod. Do asynchronicznego sprawdzenia dostępności pliku można użyć `fs.promises.access()`.

import { access } from 'node:fs/promises';

async function fileExists(filePath) {
  try {
    await access(filePath);
    return true;
  } catch {
    return false;
  }
}

const available = await fileExists('./public/logo.svg');
console.log(available);

Funkcja `access()` nie zwraca `true` lub `false`. Gdy operacja się powiedzie, kończy się bez wyjątku, a gdy pliku nie ma albo proces nie ma odpowiednich uprawnień, trafiamy do bloku `catch`. To podejście dobrze pasuje do warstwy serwisowej, middleware oraz endpointów zwracających pliki.

Można też sprawdzić konkretny poziom dostępu. Stała `F_OK` oznacza samo istnienie, a `R_OK` i `W_OK` odpowiednio możliwość odczytu i zapisu.

import { access, constants } from 'node:fs/promises';

try {
  await access('./uploads/report.pdf', constants.R_OK);
  console.log('Plik można odczytać');
} catch (error) {
  console.error('Plik nie istnieje albo nie jest dostępny');
}

Trzeba jednak rozróżnić istnienie pliku od jego dostępności. Plik może istnieć, ale konto uruchamiające Node.js może nie mieć prawa do odczytu. W aplikacji webowej ten szczegół często tłumaczy sytuacje, w których ścieżka wygląda poprawnie, a odpowiedź serwera nadal kończy się błędem.

Kiedy lepiej od razu otworzyć lub odczytać plik

Najczęstszy błąd wygląda tak: najpierw sprawdzamy, czy plik istnieje, a chwilę później próbujemy go odczytać. Między tymi dwiema operacjami plik może zostać usunięty, zmieniony albo zablokowany. To klasyczny problem race condition, czyli sytuacja, w której wynik pierwszej operacji przestaje być aktualny przed wykonaniem drugiej.

Jeśli celem jest odczyt, zwykle piszę kod wprost i obsługuję błąd operacji końcowej.

import { readFile } from 'node:fs/promises';

async function loadConfig() {
  try {
    const content = await readFile('./data/config.json', 'utf8');
    return JSON.parse(content);
  } catch (error) {
    if (error.code === 'ENOENT') {
      return null;
    }

    throw error;
  }
}

W tym przykładzie `ENOENT` oznacza, że wskazany plik lub katalog nie istnieje. Inne błędy, na przykład brak uprawnień albo uszkodzenie danych JSON, są ponownie rzucane, bo nie powinny zostać cicho zamaskowane. Obsługuję tylko te błędy, które rzeczywiście potrafię zinterpretować.

Podobnie wygląda zapis. Nie sprawdzam wcześniej, czy plik istnieje, jeśli i tak chcę go utworzyć lub nadpisać. Dla operacji wymagającej utworzenia nowego pliku można zastosować tryb `wx`, który zgłosi błąd, gdy plik już istnieje.

import { writeFile } from 'node:fs/promises';

try {
  await writeFile('./data/new-report.txt', 'Treść raportu', {
    encoding: 'utf8',
    flag: 'wx'
  });

  console.log('Plik został utworzony');
} catch (error) {
  if (error.code === 'EEXIST') {
    console.log('Plik już istnieje');
  } else {
    throw error;
  }
}

Takie podejście jest bezpieczniejsze niż para `exists()` plus `writeFile()`, bo decyzja o utworzeniu pliku odbywa się w ramach właściwej operacji. W kodzie produkcyjnym ta różnica ma większe znaczenie niż kilka dodatkowych wierszy.

Ścieżki, typy i błędy, które zmieniają wynik

Ścieżka względna zależy od katalogu procesu

Ścieżka `./data/file.txt` jest liczona względem bieżącego katalogu procesu, a niekoniecznie względem pliku JavaScript, w którym znajduje się kod. Dlatego aplikacja uruchomiona z innego miejsca może nagle „stracić” plik, który lokalnie działał bez problemu.

W projektach ESM często buduję ścieżkę na podstawie `import.meta.url`. Dzięki temu lokalizacja pliku nie zależy od miejsca, z którego uruchomiono polecenie `node`.

import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { existsSync } from 'node:fs';

const currentFile = fileURLToPath(import.meta.url);
const currentDirectory = path.dirname(currentFile);
const configPath = path.join(currentDirectory, 'data', 'config.json');

console.log(existsSync(configPath));

W aplikacji serwerowej ścieżki pochodzące od użytkownika trzeba dodatkowo walidować. Samo sprawdzenie istnienia nie chroni przed próbą odwołania do niepożądanych lokalizacji, na przykład przez fragment `../`. Kontrola ścieżki i uprawnień jest osobnym zadaniem bezpieczeństwa.

Plik, katalog i dowiązanie symboliczne

`existsSync()` odpowiada na pytanie, czy coś znajduje się pod daną ścieżką, ale nie określa rodzaju obiektu. Gdy potrzebuję rozróżnić plik i katalog, korzystam z `stat()` lub `lstat()`. Pierwsze zwykle podąża za dowiązaniem symbolicznym, a drugie pozwala sprawdzić samo dowiązanie.

import { stat } from 'node:fs/promises';

try {
  const info = await stat('./uploads');

  if (info.isDirectory()) {
    console.log('To jest katalog');
  } else if (info.isFile()) {
    console.log('To jest plik');
  }
} catch (error) {
  if (error.code === 'ENOENT') {
    console.log('Ścieżka nie istnieje');
  } else {
    throw error;
  }
}

Przeczytaj również: Blazor Server w .NET - kiedy warto wybrać interaktywność serwerową?

Brak pliku to nie zawsze jedyny problem

Warto odróżnić kilka typowych kodów błędów. `ENOENT` oznacza brak ścieżki, `EACCES` lub `EPERM` wskazuje na problem z uprawnieniami, a `EISDIR` może pojawić się wtedy, gdy kod próbuje czytać katalog jak zwykły plik. Taka klasyfikacja daje użytkownikowi lepszy komunikat i ułatwia diagnozowanie wdrożenia.

Metoda Charakterystyka Najlepsze zastosowanie
existsSync() Synchroniczna, zwraca boolean Skrypty i kod wykonywany podczas startu
fs.promises.access() Asynchroniczna, zgłasza wyjątek przy problemie Samo sprawdzenie istnienia lub uprawnień
fs.promises.stat() Asynchroniczna, zwraca informacje o obiekcie Rozróżnienie pliku, katalogu i metadanych
readFile() lub writeFile() Wykonuje docelową operację i obsługuje jej wynik Gdy sprawdzenie jest tylko etapem przed odczytem lub zapisem

Jak wybrać metodę do konkretnego scenariusza

Dobór rozwiązania jest prostszy, gdy najpierw określę, co ma się wydarzyć po sprawdzeniu. Samo pytanie o istnienie pliku wymaga innej metody niż wczytanie konfiguracji, wysłanie obrazu do klienta czy bezpieczne utworzenie raportu.

  • Do prostego warunku w skrypcie używam existsSync().
  • Do asynchronicznego sprawdzenia istnienia wybieram fs.promises.access().
  • Do rozpoznania typu ścieżki korzystam z stat() i `isFile()` albo `isDirectory()`.
  • Przed odczytem pliku zwykle pomijam osobny test i obsługuję ENOENT z `readFile()`.
  • Przy tworzeniu nowego pliku stosuję odpowiedni tryb zapisu, na przykład flag: 'wx'.

Nie używam asynchronicznego `fs.exists()`, mimo że można go znaleźć w starszych przykładach. Ta funkcja jest oznaczona jako przestarzała, a jej callback różni się od typowego wzorca Node.js. W nowych projektach lepiej trzymać się API opartego na `promises`, `access()` i bezpośredniej obsłudze błędów.

W endpointach, które obsługują wiele żądań, szczególnie ważne jest unikanie synchronicznych operacji na dużych plikach. Jednorazowe sprawdzenie podczas uruchamiania serwera raczej nie będzie problemem, ale ten sam kod wykonywany przy każdym żądaniu może pogorszyć czas odpowiedzi pod obciążeniem.

Mały test pliku, duża różnica w stabilności aplikacji

Najkrótsza odpowiedź brzmi: użyj `existsSync()`, gdy potrzebujesz prostego synchronicznego sprawdzenia, oraz `fs.promises.access()`, gdy kod ma działać asynchronicznie. Jeżeli po sprawdzeniu i tak odczytujesz albo zapisujesz plik, najczęściej rozsądniej wykonać tę operację od razu i poprawnie obsłużyć jej błąd.

Właśnie takie drobne decyzje odróżniają działający przykład od kodu, który dobrze zachowuje się w aplikacji webowej. Stabilna implementacja uwzględnia nie tylko istnienie ścieżki, ale też jej typ, uprawnienia, sposób wyznaczania lokalizacji oraz możliwość, że stan systemu plików zmieni się między dwiema operacjami.

FAQ - Najczęstsze pytania

existsSync() sprawdza synchronicznie, czy podana ścieżka istnieje, i zwraca wartość true lub false. Dobrze pasuje do krótkich skryptów, konfiguracji oraz kodu wykonywanego podczas uruchamiania aplikacji, ale w obsłudze żądań HTTP może blokować event loop.

fs.promises.access() działa asynchronicznie i kończy się bez wyjątku, gdy ścieżka jest dostępna. W przypadku braku pliku lub uprawnień zgłasza błąd obsługiwany w bloku catch. Można użyć stałych F_OK, R_OK i W_OK, aby sprawdzać istnienie, odczyt lub zapis.

Osobne sprawdzenie może prowadzić do race condition, ponieważ plik między testem a odczytem może zostać usunięty lub zmieniony. Zwykle lepiej od razu wywołać readFile() i obsłużyć błąd ENOENT. Inne błędy, takie jak brak uprawnień lub nieprawidłowy JSON, należy ponownie rzucić.

Samo existsSync() potwierdza istnienie obiektu, ale nie rozróżnia pliku i katalogu. Do tego celu należy użyć stat() lub statSync() oraz metod isFile() albo isDirectory(). W przypadku dowiązań symbolicznych lstat() pozwala sprawdzić samo dowiązanie.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

node.js
system plików
ścieżki
uprawnienia
programowanie asynchroniczne
Autor Bruno Krawczyk
Bruno Krawczyk
Mam na imię Bruno i od 8 lat zgłębiam tajniki programowania w ekosystemie .NET, chmury Azure oraz sztucznej inteligencji. Moja przygoda z technologią zaczęła się od ciekawości, jak złożone systemy mogą ułatwiać codzienne życie i rozwiązywać realne problemy. Dziś moją misją jest dzielenie się tą wiedzą, starając się przybliżyć nawet najbardziej skomplikowane zagadnienia w sposób zrozumiały i przystępny dla każdego. W moich artykułach na kursdotnet.pl skupiam się na praktycznych aspektach, analizuję najnowsze trendy i weryfikuję informacje, aby dostarczyć Wam treści, które są nie tylko dokładne i aktualne, ale przede wszystkim użyteczne w Waszej własnej ścieżce rozwoju technologicznego.

Udostępnij artykuł

Napisz komentarz