Skip to main content

Overview

API Master uses standard HTTP status codes and JSON error responses to communicate errors to clients. Understanding the error handling patterns will help you build more reliable applications.

HTTP Status Codes

The API uses standard HTTP status codes to indicate the success or failure of requests:
Status CodeMeaningUse Case
200 OKSuccessRequest completed successfully
400 Bad RequestClient ErrorInvalid request data or missing required fields
404 Not FoundNot FoundResource doesn’t exist
500 Internal Server ErrorServer ErrorUnexpected server-side error

Error Response Format

All errors follow a consistent JSON structure:
{
  "message": "Description of what went wrong"
}
The message field provides human-readable information about the error, making it easy to display to users or log for debugging.

File Upload Error Handling

No File Uploaded Error

The most common error occurs when a client attempts to upload without including a file. Here’s the actual implementation from userController.ts:
userController.ts
import { Request, Response } from 'express';

const uploadFile = (req: Request, res: Response) => {
  if (!req.file) {
    return res.status(400).json({ message: 'No file uploaded' });
  }
  // Construct the public URL dynamically
  const fileUrl = `${req.protocol}://${req.get('host')}/uploads/${req.file.filename}`;
  res.json({ message: 'File uploaded successfully', url: fileUrl });
};

export default {
  uploadFile
};

Error Example

Request:
curl -X POST http://localhost:3000/users/upload
Response:
{
  "message": "No file uploaded"
}
HTTP Status: 400 Bad Request
The controller checks req.file to ensure a file was included in the request. If missing, it immediately returns a 400 error before any processing occurs.

Common Error Scenarios

Scenario: Client sends a POST request without the file fieldRequest:
const formData = new FormData();
// Forgot to append file!

fetch('http://localhost:3000/users/upload', {
  method: 'POST',
  body: formData
});
Response:
{
  "message": "No file uploaded"
}
Status Code: 400Solution: Always append the file with the correct field name:
formData.append('file', fileObject);
Scenario: Client uses incorrect field name (e.g., document instead of file)Request:
curl -X POST http://localhost:3000/users/upload \
  -F "[email protected]"  # Wrong field name!
Response:
{
  "message": "No file uploaded"
}
Status Code: 400Solution: The field name must match the Multer configuration:
upload.single('file')  // Field name is 'file'
Use the correct field name:
curl -X POST http://localhost:3000/users/upload \
  -F "[email protected]"  # Correct!
Scenario: Browser blocks request due to CORS policyError Message in Console:
Access to fetch at 'http://localhost:3000/users/upload' from origin 'http://localhost:5173' 
has been blocked by CORS policy
Solution: Ensure CORS is properly configured (see CORS Configuration)
const corsOptions = {
  origin: '*', // Or specific origin
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
};

app.use(cors(corsOptions));
Scenario: Attempting to access a file that doesn’t existRequest:
curl http://localhost:3000/uploads/nonexistent-file.jpg
Response: Default Express 404 response (varies)Status Code: 404Solution: Verify the file URL is correct and the file was successfully uploaded
Scenario: Calling a non-existent endpointRequest:
curl http://localhost:3000/users/invalid-endpoint
Response: Default Express 404 responseStatus Code: 404Solution: Verify you’re using the correct endpoint. Available routes:
  • POST /users/upload
  • GET /uploads/{filename} (static files)

Multer Error Handling

Multer can throw specific errors that should be handled. Here’s how to implement comprehensive Multer error handling:
userRoutes.ts
import express from 'express';
import userController from '../controllers/userController';
import multer from 'multer';
import path from 'path';

const router = express.Router();

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, path.join(__dirname, '../../uploads'));
  },
  filename: (req, file, cb) => {
    const uniqueName = 'file-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9) + path.extname(file.originalname);
    cb(null, uniqueName);
  }
});

const upload = multer({ 
  storage: storage,
  limits: {
    fileSize: 5 * 1024 * 1024 // 5MB limit
  },
  fileFilter: (req, file, cb) => {
    const allowedTypes = /jpeg|jpg|png|gif|pdf/;
    const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
    const mimetype = allowedTypes.test(file.mimetype);
    
    if (mimetype && extname) {
      return cb(null, true);
    } else {
      cb(new Error('Invalid file type. Only JPEG, PNG, GIF, and PDF are allowed.'));
    }
  }
});

router.post('/upload', (req, res, next) => {
  upload.single('file')(req, res, (err) => {
    if (err instanceof multer.MulterError) {
      // Multer-specific errors
      if (err.code === 'LIMIT_FILE_SIZE') {
        return res.status(400).json({ 
          message: 'File too large. Maximum size is 5MB.' 
        });
      }
      if (err.code === 'LIMIT_UNEXPECTED_FILE') {
        return res.status(400).json({ 
          message: 'Unexpected field name. Use "file" as the field name.' 
        });
      }
      return res.status(400).json({ 
        message: `Upload error: ${err.message}` 
      });
    } else if (err) {
      // Other errors (e.g., file type validation)
      return res.status(400).json({ 
        message: err.message 
      });
    }
    // No error, proceed to controller
    next();
  });
}, userController.uploadFile);

export default router;

Multer Error Types

Error CodeDescriptionStatus
LIMIT_FILE_SIZEFile exceeds size limit400
LIMIT_FILE_COUNTToo many files400
LIMIT_UNEXPECTED_FILEUnexpected field name400
LIMIT_FIELD_KEYField name too long400
LIMIT_FIELD_VALUEField value too long400
LIMIT_FIELD_COUNTToo many fields400
LIMIT_PART_COUNTToo many parts400

Client-Side Error Handling

Proper Error Handling with Fetch

async function uploadFile(file) {
  const formData = new FormData();
  formData.append('file', file);

  try {
    const response = await fetch('http://localhost:3000/users/upload', {
      method: 'POST',
      body: formData
    });

    const data = await response.json();

    if (!response.ok) {
      // Server returned an error status
      throw new Error(data.message || 'Upload failed');
    }

    console.log('Success:', data.message);
    console.log('File URL:', data.url);
    return data;

  } catch (error) {
    // Network error or thrown error
    console.error('Upload error:', error.message);
    throw error;
  }
}

Error Handling with Axios

import axios from 'axios';

async function uploadFile(file) {
  const formData = new FormData();
  formData.append('file', file);

  try {
    const response = await axios.post(
      'http://localhost:3000/users/upload',
      formData,
      {
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      }
    );

    console.log('Success:', response.data.message);
    return response.data;

  } catch (error) {
    if (error.response) {
      // Server responded with error status
      console.error('Server error:', error.response.data.message);
      console.error('Status:', error.response.status);
    } else if (error.request) {
      // Request made but no response received
      console.error('No response from server');
    } else {
      // Something else went wrong
      console.error('Error:', error.message);
    }
    throw error;
  }
}

Request Validation Best Practices

1

Validate Early

Check for required data at the beginning of your handler:
const uploadFile = (req: Request, res: Response) => {
  if (!req.file) {
    return res.status(400).json({ message: 'No file uploaded' });
  }
  // Continue processing...
};
2

Use Middleware for Common Validations

Create reusable validation middleware:
const validateFileUpload = (req: Request, res: Response, next: Function) => {
  if (!req.file) {
    return res.status(400).json({ message: 'No file uploaded' });
  }
  next();
};

router.post('/upload', upload.single('file'), validateFileUpload, userController.uploadFile);
3

Provide Clear Error Messages

Error messages should be:
  • Descriptive
  • Actionable
  • User-friendly
Good: "No file uploaded. Please select a file and try again."Bad: "Error"
4

Log Errors for Debugging

Log errors server-side while returning sanitized messages to clients:
const uploadFile = (req: Request, res: Response) => {
  try {
    if (!req.file) {
      console.log('Upload attempt without file');
      return res.status(400).json({ message: 'No file uploaded' });
    }
    // Process upload...
  } catch (error) {
    console.error('Upload error:', error);
    res.status(500).json({ message: 'Internal server error' });
  }
};

Global Error Handler

Implement a global error handler to catch unhandled errors:
app.ts
import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';
import path from 'path';
import userRoutes from './src/routes/userRoutes';

const app = express();

// CORS Configuration
const corsOptions = {
  origin: '*',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
};

// Middleware
app.use(cors(corsOptions));
app.use(express.json());
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));

// Routes
app.use('/users', userRoutes);

// Default route
app.get('/', (req: Request, res: Response) => {
  res.json({ message: 'Welcome to API Master' });
});

// 404 Handler
app.use((req: Request, res: Response) => {
  res.status(404).json({ 
    message: 'Route not found',
    path: req.path
  });
});

// Global Error Handler
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error('Unhandled error:', err);
  
  res.status(500).json({ 
    message: 'An unexpected error occurred',
    error: process.env.NODE_ENV === 'development' ? err.message : undefined
  });
});

// Start server
const PORT = process.env.PORT || 3000;
if (!module.parent) {
  app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
  });
}

export default app;
In production, avoid exposing detailed error information. Use generic messages and log details server-side for debugging.

Testing Error Scenarios

Test No File Upload

curl -X POST http://localhost:3000/users/upload -v
Expected: 400 status with {"message": "No file uploaded"}

Test Wrong Field Name

curl -X POST http://localhost:3000/users/upload \
  -F "[email protected]" -v
Expected: 400 status with {"message": "No file uploaded"}

Test Successful Upload

curl -X POST http://localhost:3000/users/upload \
  -F "[email protected]" -v
Expected: 200 status with success response

Test File Access

# First upload a file, then access it
curl http://localhost:3000/uploads/file-1709740800000-x5k9m2j4p.jpg -v
Expected: 200 status with file content

Best Practices Summary

Check for required data before processing requests. Return early with clear error messages.
  • 400 for client errors (bad input)
  • 404 for not found
  • 500 for server errors
Tell users what went wrong and how to fix it:Good: "File too large. Maximum size is 5MB."Bad: "Error 413"
Catch errors and provide feedback to users instead of silent failures.
Log detailed error information server-side while returning sanitized messages to clients.
Regularly test error paths to ensure they behave correctly.
Never expose sensitive information (stack traces, database errors, file paths) in production error responses.

Build docs developers (and LLMs) love