Skip to main content

Overview

The Dental Odontogram application uses Cloudinary as the image storage service for odontogram PNG files. Cloudinary provides reliable, scalable cloud-based image management with CDN delivery.

Environment Variables

To integrate Cloudinary, you need to configure the following environment variables:
CLOUDINARY_CLOUD_NAME
string
required
Your Cloudinary cloud name (found in your Cloudinary dashboard)
CLOUDINARY_API_KEY
string
required
Your Cloudinary API key for authentication
CLOUDINARY_API_SECRET
string
required
Your Cloudinary API secret for secure authentication

Setting Up Environment Variables

Create a .env.local file in your project root:
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret
Never commit your .env.local file to version control. Add it to your .gitignore file.

Configuration

The Cloudinary SDK is configured in the upload endpoint with the following settings:
import { v2 as cloudinary } from 'cloudinary'

// Configure Cloudinary
cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
  secure: true,
})

Configuration Options

  • cloud_name - Identifies your Cloudinary account
  • api_key - Public key for API authentication
  • api_secret - Private key for secure authentication
  • secure - When set to true, uses HTTPS for all uploads and URLs

How Images Are Uploaded

The upload process uses Cloudinary’s upload stream API with custom options:
const uploadOptions = {
  resource_type: 'image',
  format: 'png',
  quality: 'auto:best',
  public_id: `odontograms/${recordId}_${Date.now()}`,
  folder: 'odontograms',
  context: {
    patient_record: recordId,
    upload_date: new Date().toISOString(),
    original_name: file.originalFilename,
  },
}

const uploadResult = await new Promise((resolve, reject) => {
  const uploadStream = cloudinary.uploader.upload_stream(
    uploadOptions,
    (error, result) => {
      if (error) {
        console.error('❌ Cloudinary error:', error)
        reject(error)
      } else {
        resolve(result)
      }
    }
  )
  uploadStream.end(originalBuffer)
})

Upload Options Explained

resource_type
string
default:"image"
Specifies the type of file being uploaded
format
string
default:"png"
Forces the output format to PNG
quality
string
default:"auto:best"
Automatically optimizes quality while maintaining visual fidelity
public_id
string
Unique identifier for the image: odontograms/{recordId}_{timestamp}
folder
string
default:"odontograms"
Organizes images into the ‘odontograms’ folder in Cloudinary
context
object
Metadata attached to the image:
  • patient_record - Airtable record ID
  • upload_date - ISO timestamp
  • original_name - Original filename

Upload Result

After a successful upload, Cloudinary returns an object containing:
{
  public_id: 'odontograms/recABCDEF123456_1234567890',
  secure_url: 'https://res.cloudinary.com/your-cloud/image/upload/v1234567890/odontograms/recABCDEF123456_1234567890.png',
  url: 'http://res.cloudinary.com/your-cloud/image/upload/v1234567890/odontograms/recABCDEF123456_1234567890.png',
  format: 'png',
  width: 1200,
  height: 800,
  bytes: 245678,
  // ... other metadata
}
The secure_url is stored in Airtable as the image reference.

Getting Your Cloudinary Credentials

  1. Sign up for a free Cloudinary account at cloudinary.com
  2. Navigate to Dashboard - After logging in, you’ll see your credentials
  3. Copy credentials:
    • Cloud Name
    • API Key
    • API Secret
  4. Add to environment variables as shown above

Free Tier Limits

Cloudinary’s free tier includes:
  • 25 GB storage
  • 25 GB monthly bandwidth
  • 25,000 monthly transformations
This is sufficient for most small to medium dental practices.

Error Handling

The application validates Cloudinary credentials before attempting uploads:
if (
  !process.env.CLOUDINARY_CLOUD_NAME ||
  !process.env.CLOUDINARY_API_KEY ||
  !process.env.CLOUDINARY_API_SECRET
) {
  return res
    .status(500)
    .json({ error: 'Cloudinary credentials not configured' })
}
If an upload fails, the error is caught and returned:
if (error) {
  console.error('❌ Cloudinary error:', error)
  reject(error)
}

Security Best Practices

  1. Never expose API secrets in client-side code
  2. Use environment variables for all credentials
  3. Enable secure URLs with secure: true
  4. Rotate credentials periodically
  5. Monitor usage in Cloudinary dashboard to detect unusual activity

Image Organization

All odontogram images are stored in the odontograms/ folder with the naming convention:
odontograms/{recordId}_{timestamp}.png
This ensures:
  • Easy identification of patient records
  • No naming conflicts
  • Chronological ordering of uploads

Next Steps

Build docs developers (and LLMs) love