KAHIBARO
Discord Login Register

File Handling

Why File Handling Matters for Backend Development

Backends constantly work with files. Even if you do not think about it, you are dealing with files when you:

Understanding file handling at the programming fundamentals level prepares you for all these tasks, no matter which backend framework or language you use later.

In this chapter, you will focus on:

You will see examples in a generic pseudocode style, then recognize the same patterns easily in real backend languages like Python, JavaScript, or Go.

Key idea: File handling is about opening a file, doing something with it, and closing it correctly, while handling errors and avoiding data corruption.


Files and Paths

What Is a File?

A file is a named collection of data stored on a disk or other storage device.

Some important properties of a file:

Backend code usually cares most about the path and the content.

Text files vs binary files

You handle text and binary files slightly differently, especially regarding encoding.


Paths: Absolute and Relative

The path tells your program where a file lives.

Example directory structure:

text
/home/backend/app/
  β”œβ”€β”€ main.py
  β”œβ”€β”€ config/
  β”‚     └── settings.json
  └── logs/
        └── app.log

Absolute paths

Absolute paths start at the root of the file system.

Examples on Linux or macOS:

Examples on Windows:

They always point to the same place, no matter where your program starts, but they make your code less portable and harder to deploy across machines.

Relative paths

Relative paths start from the current working directory of your program.

If your current directory is /home/backend/app:

Relative paths make your code easier to move between environments, as long as your project structure is consistent.

Parent and current directory symbols

You will often see:

Examples:

Path Handling in Code

You should avoid building paths by manual string concatenation such as:

pseudo
path = "logs/" + filename

This can break on different operating systems, because directory separators differ, for example / on Linux and \ on Windows.

Most languages provide helpers to safely build paths.

Example in pseudocode:

pseudo
path = join_path("logs", "app.log")

Under the hood, join_path chooses the correct separator for the current operating system.

Whenever you need to:

prefer a built-in path utility. This becomes very important when you deploy your backend to Linux servers while developing on Windows or macOS.


Opening and Closing Files

Any file operation follows a simple life cycle:

  1. Open the file
  2. Read or write or append
  3. Close the file

If you forget to close a file, you can:

File Modes

When you open a file, you must specify a mode that tells the system what you want to do.

Common modes:

ModePurposeFile must exist?Content preserved?
rRead textYesYes
wWrite text, overwrite fileNoNo, file is truncated to zero length
aAppend text to end of fileNoYes, new data added at the end
r+Read and write, no truncationYesYes
w+Read and write, overwrite fileNoNo, content is cleared when opened
bBinary flag, combined with othersDependsDepends, same as base mode

Examples:

Many languages use a similar set of modes.

Very important rule:
Opening a file with w or w+ deletes its existing content immediately. Use it only when you are sure you want to replace everything.


Basic Open / Read / Close

Pseudocode for safely reading a file:

pseudo
file = open("config/settings.json", mode="r")
content = file.read()
file.close()

This pattern works, but has a problem. If something goes wrong between open and close, for example:

then file.close() might never be called.


Context Managers (Safe Pattern)

Most languages offer a construct that guarantees closing the file automatically. In Python this is with, in other languages you may see using, defer, or similar.

Pseudocode:

pseudo
with open("logs/app.log", mode="a") as file:
    file.write("Application started\n")

When the with block ends, the file is closed, even if an error occurs inside the block.

Always prefer this pattern in backend code, especially when you may open many files inside loops or request handlers.


Reading Files

Backend services read files to get configuration, templates, data seeds, or imports.

There are several reading strategies, depending on file size and use case.


Reading Entire Content

Use this only for relatively small files, for example configuration files.

pseudo
with open("config/settings.json", "r") as file:
    content = file.read()
print(content)

Now content holds the full file as a single string.

Example: Load a SQL seed script

pseudo
with open("db/seed.sql", "r") as file:
    seed_sql = file.read()
execute_sql(seed_sql)

Reading Line by Line

When you have large text files, especially logs or CSV data, you should process them line by line to avoid loading the entire file into memory.

pseudo
with open("logs/access.log", "r") as file:
    for line in file:
        process_log_line(line)

Here, process_log_line might:

You only have one line in memory at a time, which is efficient for large files.


Reading Fixed Size Chunks

Chunked reading is useful when dealing with binary files, such as large images or videos, or streaming files over HTTP.

pseudo
chunk_size = 4096  # 4 KB
with open("videos/tutorial.mp4", "rb") as file:
    while True:
        chunk = file.read(chunk_size)
        if not chunk:
            break
        send_over_network(chunk)

Advantages:

Backend download endpoints often work internally with chunked reading.


Handling Encodings

Text files are stored as bytes, and encoding describes how characters map to bytes.

Common encodings:

If you read a UTF 8 encoded file with the wrong encoding, you might get:

Many languages let you specify encoding when opening the file:

pseudo
with open("config/settings.json", "r", encoding="utf-8") as file:
    content = file.read()

For backend work, you should usually standardize on UTF 8.

Rule of thumb:
Unless you have a very specific reason, always use UTF 8 for reading and writing text files.


Writing and Appending to Files

Backends write to files in many situations:

Writing Text Files

When you open a file in write mode, the previous content is erased.

pseudo
with open("logs/app.log", "w") as file:
    file.write("Application started\n")
    file.write("Another log entry\n")

After this code, app.log contains exactly two lines. If it existed before, its old content is gone.

Example: Export user data as CSV

Imagine you have a list of users:

pseudo
users = [
    {"id": 1, "email": "alice@example.com"},
    {"id": 2, "email": "bob@example.com"},
]

You can export them:

pseudo
with open("exports/users.csv", "w", encoding="utf-8") as file:
    file.write("id,email\n")
    for user in users:
        line = user["id"] + "," + user["email"] + "\n"
        file.write(line)

Resulting users.csv:

text
id,email
1,alice@example.com
2,bob@example.com

Appending to Files

Appending is used heavily for logging and any situation where you want to add new data without losing old data.

pseudo
with open("logs/app.log", "a", encoding="utf-8") as file:
    file.write("2026-08-27 10:00:00 - User login: alice\n")

Each time this code runs, a new line is added to the end of the file.

Example: Simple request logger

pseudo
def log_request(method, path, status_code):
    line = method + " " + path + " " + str(status_code) + "\n"
    with open("logs/access.log", "a", encoding="utf-8") as file:
        file.write(line)

You could call this from your HTTP handler:

pseudo
log_request("GET", "/users/1", 200)

Writing Binary Files

For binary content, such as image uploads, use binary mode.

pseudo
def save_uploaded_image(file_bytes, filename):
    with open("uploads/" + filename, "wb") as file:
        file.write(file_bytes)

Here file_bytes is a sequence of bytes from a user upload.


Overwriting vs Updating

Sometimes you need to change a file in place, for example update one line.

A simple pattern:

  1. Read original file line by line
  2. Modify each line as needed
  3. Write to a temporary file
  4. Replace original file with temporary file

Example in pseudocode:

pseudo
with open("data/users.txt", "r") as source, open("data/users.tmp", "w") as target:
    for line in source:
        updated_line = transform(line)
        target.write(updated_line)
rename("data/users.tmp", "data/users.txt")

This pattern reduces the risk of corrupting your original file if something goes wrong.


Working with CSV and JSON Files

You will frequently see CSV and JSON in backend development.


CSV: Comma Separated Values

CSV files represent rows of data, often used for imports and exports to spreadsheets.

Example users.csv:

text
id,email,age
1,alice@example.com,30
2,bob@example.com,25

To read:

pseudo
with open("users.csv", "r", encoding="utf-8") as file:
    header = file.readline().strip()
    for line in file:
        fields = line.strip().split(",")
        user_id = fields[0]
        email = fields[1]
        age = fields[2]
        save_user(user_id, email, age)

In real code, you will use a CSV library that handles:

but the basic idea remains the same.

To write:

pseudo
with open("export.csv", "w", encoding="utf-8") as file:
    file.write("id,email,age\n")
    for user in users:
        line = user.id + "," + user.email + "," + str(user.age) + "\n"
        file.write(line)

JSON: JavaScript Object Notation

JSON is the most common format for APIs and configuration in modern backends.

Example config.json:

json
{
  "database": {
    "host": "localhost",
    "port": 5432
  },
  "debug": true
}

To read JSON, you usually:

  1. Read the file as text
  2. Parse the JSON string into a data structure (dictionary / object)
pseudo
with open("config.json", "r", encoding="utf-8") as file:
    text = file.read()
config = parse_json(text)
db_host = config["database"]["host"]
db_port = config["database"]["port"]

To write JSON:

pseudo
config = {
    "database": {
        "host": "db.example.com",
        "port": 5432
    },
    "debug": false
}
text = to_json(config, indent=2)
with open("config.json", "w", encoding="utf-8") as file:
    file.write(text)

Here indent=2 pretty prints the JSON for humans, which is useful for configuration and logs.


File System Operations

Backend services often need to manage directories and check for file existence.


Checking If a File Exists

Before reading or writing, you might want to test whether a file is present.

pseudo
if file_exists("config/settings.json"):
    with open("config/settings.json", "r") as file:
        content = file.read()
else:
    print("Settings file missing")

This avoids errors like "No such file or directory".


Creating Directories

If your backend writes files to a directory that may not exist yet, create the directory first.

pseudo
if not dir_exists("logs"):
    create_directory("logs")
with open("logs/app.log", "a") as file:
    file.write("Server started\n")

Often there is a function to create nested directories in one call, such as create_all_directories("data/imports/2026").


Listing Files

Sometimes you need to read all files from a directory, for example process all uploaded files that are waiting for a background job.

pseudo
files = list_files("uploads")
for filename in files:
    process_file("uploads/" + filename)

You can filter by extension:

pseudo
files = list_files("uploads")
for filename in files:
    if filename.endswith(".jpg") or filename.endswith(".png"):
        process_image("uploads/" + filename)

Renaming and Deleting

Renaming:

pseudo
rename("logs/app.log", "logs/app-old.log")

Deleting:

pseudo
delete_file("logs/app-old.log")

Use these carefully in backend code, especially in production. Deleting the wrong file can be very costly.


Error Handling in File Operations

File operations fail for many reasons:

You must always assume that file operations can fail and handle errors gracefully.


Typical Error Handling Pattern

Basic idea in pseudocode:

pseudo
try:
    with open("config/settings.json", "r") as file:
        content = file.read()
    config = parse_config(content)
except FileNotFoundError:
    log_error("Config file not found")
    use_default_config()
except PermissionError:
    log_error("No permission to read config file")
    stop_application()
except Exception as e:
    log_error("Unexpected error: " + str(e))
    stop_application()

For backend configuration, failing fast with a clear error is often better than running with unknown settings.


Validating Data after Reading

Even if the file is read successfully, its content might be invalid:

Always validate the content after reading.

Example:

pseudo
config = parse_json(content)
if "database" not in config or "host" not in config["database"]:
    log_error("Invalid config: database.host missing")
    stop_application()

File Handling Best Practices for Backends

To summarize and highlight what matters most for backend development:

Critical file handling rules:

  1. Always use a context manager (or equivalent) so files are closed automatically.
  2. Be very careful with w and w+; they erase existing content immediately.
  3. Use UTF 8 encoding for text files unless you have a strong reason not to.
  4. Do not build paths with plain string concatenation. Use path utilities.
  5. Prefer line by line or chunked reading for large files.
  6. Always handle possible errors: missing files, permissions, corrupted data.
  7. Validate the data you read, especially for configuration and imports.
  8. Do not store sensitive secrets in plain text files without protection.

Simple Backend Style Examples

To connect everything to backend scenarios, here are some small, realistic examples.


Loading Configuration with Fallback

pseudo
DEFAULT_CONFIG = {
    "db_host": "localhost",
    "db_port": 5432,
    "debug": false
}
def load_config():
    if not file_exists("config.json"):
        return DEFAULT_CONFIG
    try:
        with open("config.json", "r", encoding="utf-8") as file:
            text = file.read()
        config = parse_json(text)
        return merge(DEFAULT_CONFIG, config)
    except Exception as e:
        log_error("Failed to load config.json: " + str(e))
        return DEFAULT_CONFIG

This code:

Rotating a Log File

Simple manual rotation pattern:

pseudo
MAX_LOG_SIZE = 10 * 1024 * 1024  # 10 MB
def rotate_log_if_needed():
    size = file_size("logs/app.log")
    if size > MAX_LOG_SIZE:
        rename("logs/app.log", "logs/app.log.bak")
def log_message(message):
    rotate_log_if_needed()
    with open("logs/app.log", "a", encoding="utf-8") as file:
        file.write(message + "\n")

Real applications often use built in log rotation tools, but the pattern is based on file handling operations you already know.


Importing Data from an Upload Directory

pseudo
def import_pending_files():
    if not dir_exists("imports"):
        return
    files = list_files("imports")
    for filename in files:
        full_path = join_path("imports", filename)
        try:
            with open(full_path, "r", encoding="utf-8") as file:
                for line in file:
                    process_import_line(line)
            rename(full_path, join_path("imports", "done_" + filename))
        except Exception as e:
            log_error("Failed to import " + filename + ": " + str(e))
            rename(full_path, join_path("imports", "error_" + filename))

This shows a realistic pattern:

All based on file handling fundamentals.


Understanding these core file handling concepts prepares you for many backend tasks, from configuration and logging to data import and export. In later chapters, you will see how frameworks and libraries build on these basics to offer higher level abstractions, but the underlying principles remain the same.

Views: 8

Comments

Please login to add a comment.

Don't have an account? Register now!