
## Introduction

This guide will show you how to use Rabata.io as a storage backend for your Express.js application. You'll learn how to handle file uploads, manage storage, and serve files from Rabata.io in your Express.js projects.

## Prerequisites

Before you begin, make sure you have:

- Node.js 14.x or later installed
- A Rabata.io account with access credentials
- A bucket created in your Rabata.io account
- Basic knowledge of Express.js and Node.js

## Installation

### Create a New Express.js Application

If you're starting from scratch, create a new Express.js application:

```bash
$ mkdir my-express-app
$ cd my-express-app
$ npm init -y
$ npm install express
```

### Install Required Dependencies

Install the AWS SDK for JavaScript and other necessary packages:

```bash
$ npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner multer dotenv uuid
```

### Project Structure

Create a basic project structure:

```
my-express-app/
├── config/
│   └── s3.js
├── routes/
│   └── upload.js
├── public/
│   └── index.html
├── .env
├── app.js
└── package.json
```

## Configuration

### Set Up Environment Variables

Create a `.env` file in the root of your project to store your Rabata.io credentials:

```
RABATA_ACCESS_KEY=YOUR_ACCESS_KEY
RABATA_SECRET_KEY=YOUR_SECRET_KEY
RABATA_BUCKET_NAME=your-bucket-name
RABATA_REGION=us-east-1
RABATA_ENDPOINT=https://s3.us-east-1.rabata.io
PORT=3000
```

### Create S3 Configuration

Create a configuration file for the S3 client:

```javascript
// config/s3.js
const { S3Client } = require('@aws-sdk/client-s3');
require('dotenv').config();

const s3Client = new S3Client({
  region: process.env.RABATA_REGION || 'us-east-1',
  endpoint: process.env.RABATA_ENDPOINT || 'https://s3.us-east-1.rabata.io',
  credentials: {
    accessKeyId: process.env.RABATA_ACCESS_KEY || '',
    secretAccessKey: process.env.RABATA_SECRET_KEY || ''
  },
  forcePathStyle: true // Required for Rabata.io
});

module.exports = { s3Client };
```

### Set Up Express Application

Create the main application file:

```javascript
// app.js
const express = require('express');
const path = require('path');
const uploadRoutes = require('./routes/upload');
require('dotenv').config();

const app = express();
const port = process.env.PORT || 3000;

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

// Routes
app.use('/api', uploadRoutes);

// Serve the HTML file
app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// Start the server
app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`);
});
```

## Basic Usage

### Create Upload Routes

Create a file for upload routes:

```javascript
// routes/upload.js
const express = require('express');
const multer = require('multer');
const { v4: uuidv4 } = require('uuid');
const {
  PutObjectCommand,
  GetObjectCommand,
  ListObjectsV2Command,
  DeleteObjectCommand
} = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
const { s3Client } = require('../config/s3');

const router = express.Router();
const upload = multer({ storage: multer.memoryStorage() });

// Upload a file
router.post('/upload', upload.single('file'), async (req, res) => {
  try {
    if (!req.file) {
      return res.status(400).json({ error: 'No file provided' });
    }

    const file = req.file;
    const fileName = `${uuidv4()}-${file.originalname}`;

    // Upload to Rabata.io
    await s3Client.send(
      new PutObjectCommand({
        Bucket: process.env.RABATA_BUCKET_NAME,
        Key: fileName,
        Body: file.buffer,
        ContentType: file.mimetype
      })
    );

    res.status(200).json({
      message: 'File uploaded successfully',
      fileName: fileName,
      fileUrl: `${process.env.RABATA_ENDPOINT}/${process.env.RABATA_BUCKET_NAME}/${fileName}`
    });
  } catch (error) {
    console.error('Error uploading file:', error);
    res.status(500).json({ error: 'Error uploading file' });
  }
});

// List all files
router.get('/files', async (req, res) => {
  try {
    const command = new ListObjectsV2Command({
      Bucket: process.env.RABATA_BUCKET_NAME
    });

    const response = await s3Client.send(command);

    const files = response.Contents?.map(item => ({
      key: item.Key,
      size: item.Size,
      lastModified: item.LastModified,
      url: `${process.env.RABATA_ENDPOINT}/${process.env.RABATA_BUCKET_NAME}/${item.Key}`
    })) || [];

    res.status(200).json({ files });
  } catch (error) {
    console.error('Error listing files:', error);
    res.status(500).json({ error: 'Error listing files' });
  }
});

// Delete a file
router.delete('/files/:key', async (req, res) => {
  try {
    const key = req.params.key;

    await s3Client.send(
      new DeleteObjectCommand({
        Bucket: process.env.RABATA_BUCKET_NAME,
        Key: key
      })
    );

    res.status(200).json({ message: 'File deleted successfully' });
  } catch (error) {
    console.error('Error deleting file:', error);
    res.status(500).json({ error: 'Error deleting file' });
  }
});

// Generate a presigned URL for downloading a file
router.get('/files/:key/url', async (req, res) => {
  try {
    const key = req.params.key;
    const expiresIn = parseInt(req.query.expiresIn) || 3600; // Default 1 hour

    const command = new GetObjectCommand({
      Bucket: process.env.RABATA_BUCKET_NAME,
      Key: key
    });

    const signedUrl = await getSignedUrl(s3Client, command, { expiresIn });

    res.status(200).json({ signedUrl });
  } catch (error) {
    console.error('Error generating signed URL:', error);
    res.status(500).json({ error: 'Error generating signed URL' });
  }
});

module.exports = router;
```

### Create a Simple HTML Interface

Create a simple HTML interface for file uploads:

```html
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Express.js with Rabata.io</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
    }
    h1 {
      text-align: center;
    }
    .upload-container {
      border: 1px solid #ddd;
      padding: 20px;
      border-radius: 5px;
      margin-bottom: 20px;
    }
    .form-group {
      margin-bottom: 15px;
    }
    label {
      display: block;
      margin-bottom: 5px;
    }
    .error {
      color: red;
      margin: 10px 0;
    }
    .success {
      color: green;
      margin: 10px 0;
    }
    .file-list {
      margin-top: 30px;
    }
    ul {
      list-style: none;
      padding: 0;
    }
    li {
      display: flex;
      align-items: center;
      padding: 10px;
      border-bottom: 1px solid #eee;
    }
    .file-size {
      margin: 0 10px;
      color: #666;
      font-size: 0.9em;
    }
    .delete-button {
      margin-left: auto;
      padding: 5px 10px;
      background-color: #f44336;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    button {
      padding: 8px 16px;
      background-color: #4CAF50;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    button:disabled {
      background-color: #cccccc;
      cursor: not-allowed;
    }
  </style>
</head>
<body>
  <h1>Express.js with Rabata.io</h1>

  <div class="upload-container">
    <h2>Upload File to Rabata.io</h2>

    <form id="uploadForm">
      <div class="form-group">
        <label for="file">Select File</label>
        <input type="file" id="file" name="file">
      </div>

      <div id="error" class="error" style="display: none;"></div>
      <div id="success" class="success" style="display: none;"></div>

      <button type="submit" id="uploadButton">Upload</button>
    </form>
  </div>

  <div class="file-list">
    <h2>Files in Rabata.io Bucket</h2>
    <div id="loading">Loading files...</div>
    <ul id="filesList"></ul>
  </div>

  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const uploadForm = document.getElementById('uploadForm');
      const fileInput = document.getElementById('file');
      const uploadButton = document.getElementById('uploadButton');
      const errorDiv = document.getElementById('error');
      const successDiv = document.getElementById('success');
      const filesList = document.getElementById('filesList');
      const loadingDiv = document.getElementById('loading');

      // Fetch files on page load
      fetchFiles();

      // Handle form submission
      uploadForm.addEventListener('submit', async (e) => {
        e.preventDefault();

        if (!fileInput.files || !fileInput.files[0]) {
          showError('Please select a file');
          return;
        }

        const file = fileInput.files[0];
        const formData = new FormData();
        formData.append('file', file);

        uploadButton.disabled = true;
        errorDiv.style.display = 'none';
        successDiv.style.display = 'none';

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

          const data = await response.json();

          if (!response.ok) {
            throw new Error(data.error || 'Error uploading file');
          }

          showSuccess(`File uploaded successfully! <a href="${data.fileUrl}" target="_blank">View File</a>`);
          fileInput.value = '';

          // Refresh the file list
          fetchFiles();
        } catch (error) {
          showError(error.message);
        } finally {
          uploadButton.disabled = false;
        }
      });

      // Fetch files from the server
      async function fetchFiles() {
        loadingDiv.style.display = 'block';
        filesList.innerHTML = '';

        try {
          const response = await fetch('/api/files');
          const data = await response.json();

          if (!response.ok) {
            throw new Error(data.error || 'Error fetching files');
          }

          loadingDiv.style.display = 'none';

          if (data.files.length === 0) {
            filesList.innerHTML = '<p>No files found in the bucket.</p>';
            return;
          }

          data.files.forEach(file => {
            const li = document.createElement('li');

            const link = document.createElement('a');
            link.href = file.url;
            link.target = '_blank';
            link.textContent = file.key;

            const size = document.createElement('span');
            size.className = 'file-size';
            size.textContent = `${(file.size / 1024).toFixed(2)} KB`;

            const deleteButton = document.createElement('button');
            deleteButton.className = 'delete-button';
            deleteButton.textContent = 'Delete';
            deleteButton.onclick = () => deleteFile(file.key);

            li.appendChild(link);
            li.appendChild(size);
            li.appendChild(deleteButton);

            filesList.appendChild(li);
          });
        } catch (error) {
          loadingDiv.style.display = 'none';
          filesList.innerHTML = `<p class="error">Error: ${error.message}</p>`;
        }
      }

      // Delete a file
      async function deleteFile(key) {
        if (!confirm(`Are you sure you want to delete ${key}?`)) {
          return;
        }

        try {
          const response = await fetch(`/api/files/${encodeURIComponent(key)}`, {
            method: 'DELETE'
          });

          const data = await response.json();

          if (!response.ok) {
            throw new Error(data.error || 'Error deleting file');
          }

          // Refresh the file list
          fetchFiles();
        } catch (error) {
          alert(`Error: ${error.message}`);
        }
      }

      // Show error message
      function showError(message) {
        errorDiv.textContent = message;
        errorDiv.style.display = 'block';
        successDiv.style.display = 'none';
      }

      // Show success message
      function showSuccess(message) {
        successDiv.innerHTML = message;
        successDiv.style.display = 'block';
        errorDiv.style.display = 'none';
      }
    });
  </script>
</body>
</html>
```

## Advanced Usage

### Generating Presigned URLs for Direct Uploads

For larger files, you might want to generate presigned URLs for direct uploads to Rabata.io:

```javascript
// Add this route to routes/upload.js
router.post('/presigned-upload', async (req, res) => {
  try {
    const { fileName, fileType } = req.body;

    if (!fileName || !fileType) {
      return res.status(400).json({ error: 'File name and type are required' });
    }

    const key = `${uuidv4()}-${fileName}`;

    const command = new PutObjectCommand({
      Bucket: process.env.RABATA_BUCKET_NAME,
      Key: key,
      ContentType: fileType
    });

    const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 });

    res.status(200).json({
      presignedUrl,
      key,
      fileUrl: `${process.env.RABATA_ENDPOINT}/${process.env.RABATA_BUCKET_NAME}/${key}`
    });
  } catch (error) {
    console.error('Error generating presigned URL:', error);
    res.status(500).json({ error: 'Error generating presigned URL' });
  }
});
```

### Handling Multipart Uploads

For very large files, you can implement multipart uploads:

```javascript
// Add these routes to routes/upload.js
const {
  CreateMultipartUploadCommand,
  UploadPartCommand,
  CompleteMultipartUploadCommand,
  AbortMultipartUploadCommand
} = require('@aws-sdk/client-s3');

// Initiate multipart upload
router.post('/multipart/initiate', async (req, res) => {
  try {
    const { fileName, fileType } = req.body;

    if (!fileName || !fileType) {
      return res.status(400).json({ error: 'File name and type are required' });
    }

    const key = `${uuidv4()}-${fileName}`;

    const command = new CreateMultipartUploadCommand({
      Bucket: process.env.RABATA_BUCKET_NAME,
      Key: key,
      ContentType: fileType
    });

    const response = await s3Client.send(command);

    res.status(200).json({
      uploadId: response.UploadId,
      key: key
    });
  } catch (error) {
    console.error('Error initiating multipart upload:', error);
    res.status(500).json({ error: 'Error initiating multipart upload' });
  }
});

// Get presigned URL for a part
router.post('/multipart/presigned-part', async (req, res) => {
  try {
    const { key, uploadId, partNumber } = req.body;

    if (!key || !uploadId || !partNumber) {
      return res.status(400).json({ error: 'Key, uploadId, and partNumber are required' });
    }

    const command = new UploadPartCommand({
      Bucket: process.env.RABATA_BUCKET_NAME,
      Key: key,
      UploadId: uploadId,
      PartNumber: parseInt(partNumber)
    });

    const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 });

    res.status(200).json({ presignedUrl });
  } catch (error) {
    console.error('Error generating presigned URL for part:', error);
    res.status(500).json({ error: 'Error generating presigned URL for part' });
  }
});

// Complete multipart upload
router.post('/multipart/complete', async (req, res) => {
  try {
    const { key, uploadId, parts } = req.body;

    if (!key || !uploadId || !parts || !Array.isArray(parts)) {
      return res.status(400).json({ error: 'Key, uploadId, and parts array are required' });
    }

    const command = new CompleteMultipartUploadCommand({
      Bucket: process.env.RABATA_BUCKET_NAME,
      Key: key,
      UploadId: uploadId,
      MultipartUpload: {
        Parts: parts
      }
    });

    await s3Client.send(command);

    res.status(200).json({
      message: 'Multipart upload completed successfully',
      fileUrl: `${process.env.RABATA_ENDPOINT}/${process.env.RABATA_BUCKET_NAME}/${key}`
    });
  } catch (error) {
    console.error('Error completing multipart upload:', error);
    res.status(500).json({ error: 'Error completing multipart upload' });
  }
});

// Abort multipart upload
router.post('/multipart/abort', async (req, res) => {
  try {
    const { key, uploadId } = req.body;

    if (!key || !uploadId) {
      return res.status(400).json({ error: 'Key and uploadId are required' });
    }

    const command = new AbortMultipartUploadCommand({
      Bucket: process.env.RABATA_BUCKET_NAME,
      Key: key,
      UploadId: uploadId
    });

    await s3Client.send(command);

    res.status(200).json({ message: 'Multipart upload aborted successfully' });
  } catch (error) {
    console.error('Error aborting multipart upload:', error);
    res.status(500).json({ error: 'Error aborting multipart upload' });
  }
});
```

### CORS Configuration for Direct Uploads

If you're using direct uploads with presigned URLs, you need to configure CORS for your Rabata.io bucket:

```json
[
  {
    "AllowedHeaders": [
      "Authorization",
      "Content-Type",
      "Content-Length",
      "Content-MD5",
      "x-amz-content-sha256",
      "x-amz-date",
      "x-amz-security-token",
      "x-amz-user-agent"
    ],
    "AllowedMethods": [
      "GET",
      "PUT",
      "POST",
      "DELETE"
    ],
    "AllowedOrigins": [
      "http://localhost:3000",
      "https://your-production-domain.com"
    ],
    "ExposeHeaders": [
      "ETag"
    ],
    "MaxAgeSeconds": 3600
  }
]
```

## Error Handling and Middleware

### Create Error Handling Middleware

Create a middleware for handling errors:

```javascript
// Add this to app.js
// Error handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({
    error: 'Something went wrong!',
    message: process.env.NODE_ENV === 'development' ? err.message : undefined
  });
});
```

### File Type Validation Middleware

Create a middleware for validating file types:

```javascript
// Add this to routes/upload.js
const fileTypeValidation = (allowedTypes) => {
  return (req, res, next) => {
    if (!req.file) {
      return next();
    }

    if (!allowedTypes.includes(req.file.mimetype)) {
      return res.status(400).json({
        error: 'Invalid file type',
        message: `Allowed types: ${allowedTypes.join(', ')}`
      });
    }

    next();
  };
};

// Usage example
router.post(
  '/upload/images',
  upload.single('file'),
  fileTypeValidation(['image/jpeg', 'image/png', 'image/gif']),
  async (req, res) => {
    // Handle image upload
  }
);
```

## Production Considerations

### Security Best Practices

1. **Validate File Types**: Always validate uploaded file types to prevent security issues.

2. **Set File Size Limits**: Limit the size of uploaded files:

```javascript
// Modify the multer configuration
const upload = multer({
  storage: multer.memoryStorage(),
  limits: {
    fileSize: 5 * 1024 * 1024 // 5MB
  }
});
```

3. **Use Helmet for Security Headers**:

```bash
$ npm install helmet
```

```javascript
// Add to app.js
const helmet = require('helmet');
app.use(helmet());
```

4. **Rate Limiting**:

```bash
$ npm install express-rate-limit
```

```javascript
// Add to app.js
const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
});

app.use('/api/', apiLimiter);
```

### Logging

Implement proper logging for production:

```bash
$ npm install winston
```

```javascript
// Create a logger.js file
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  defaultMeta: { service: 'express-rabata' },
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

module.exports = logger;
```

## Complete Example

Here's a complete example of an Express.js application that uses Rabata.io for file storage:

```javascript
// app.js
const express = require('express');
const path = require('path');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const uploadRoutes = require('./routes/upload');
const logger = require('./logger');
require('dotenv').config();

const app = express();
const port = process.env.PORT || 3000;

// Security middleware
app.use(helmet());

// Rate limiting
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/api/', apiLimiter);

// Basic middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

// Routes
app.use('/api', uploadRoutes);

// Serve the HTML file
app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// Error handling middleware
app.use((err, req, res, next) => {
  logger.error(err.stack);
  res.status(500).json({
    error: 'Something went wrong!',
    message: process.env.NODE_ENV === 'development' ? err.message : undefined
  });
});

// Start the server
app.listen(port, () => {
  logger.info(`Server running on http://localhost:${port}`);
});
```

## Troubleshooting

### Common Issues

1. **CORS Errors**: If you're getting CORS errors when uploading directly to Rabata.io, make sure you've configured CORS for your bucket as shown in the Advanced Usage section.

2. **Environment Variables Not Loading**: Make sure you've created a `.env` file in the root of your project and installed the dotenv package.

3. **Access Denied Errors**: Ensure your Rabata.io credentials are correct and the bucket exists.

### Debugging Tips

1. **Enable Debug Logging**:

```javascript
// Add to routes/upload.js
const debugLog = (req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next();
};

router.use(debugLog);
```

2. **Check Request and Response**:

```javascript
// Add to app.js
app.use((req, res, next) => {
  const originalSend = res.send;
  res.send = function(data) {
    console.log(`Response: ${data}`);
    originalSend.call(this, data);
  };
  next();
});
```

<div class="docs-article__actions">
  <a href="/docs/quickstart/node/" class="btn btn--secondary">Node.js Quickstart</a>
  <a href="/docs/quickstart/nextjs/" class="btn btn--secondary">Next.js Integration</a>
  <a href="/docs/quickstart/nuxt/" class="btn btn--secondary">Nuxt Integration</a>
</div>
