Skip to main content
Neon Postgres Docs
current

Search documentation

Type to search this documentation.

On this pageOverview

Media storage with ImageKit.io

ImageKit.io is a cloud based image and video optimization and delivery platform. It provides real time manipulation, storage, and delivery via a global CDN, simplifying media management for web and mo...

ImageKit.io is a cloud-based image and video optimization and delivery platform. It provides real-time manipulation, storage, and delivery via a global CDN, simplifying media management for web and mobile applications.

Neon now offers native storage


Neon Object Storage is S3-compatible object storage built into the Neon backend. Object storage branches with your database: each branch gets its own isolated namespace, so you can test file uploads in preview branches without touching production. No separate cloud account needed. Use any S3-compatible SDK with your existing Neon credential.

For more information, see Neon Object Storage.

This guide demonstrates how to integrate ImageKit.io with Neon. You'll learn how to upload files directly from the client-side to ImageKit.io using securely generated authentication parameters from your backend, and then store the resulting file metadata (like the ImageKit File ID and URL) in your Lakebase Postgres database.

  1. Create a Neon project

    1. Navigate to the Neon Console to create a new Neon project.
    2. Copy the connection string by clicking the Connect button in the Console nav. For more information, see Connect from any application.
  2. Create an ImageKit.io account and get credentials

    1. Sign up for a free or paid account at ImageKit.io.
    2. Once logged in, navigate to the Developer options section in the dashboard sidebar.
    3. Under API Keys, note your Public Key, Private Key, and URL Endpoint. These are essential for interacting with the ImageKit API and SDKs. ImageKit API Keys
  3. Create a table in Neon for file metadata

    We need a table in Lakebase Postgres to store metadata about the files uploaded to ImageKit.io. This allows your application to reference the media stored in ImageKit.

    1. Connect to your database on Neon using the Neon SQL Editor or a client like psql. Create a table to store relevant details:
      SQL
      CREATE TABLE IF NOT EXISTS imagekit_files (
          id SERIAL PRIMARY KEY,
          file_id TEXT NOT NULL UNIQUE,    -- ImageKit.io unique File ID
          file_url TEXT NOT NULL,          -- ImageKit CDN URL for the file
          user_id TEXT NOT NULL,           -- User associated with the file
          upload_timestamp TIMESTAMPTZ DEFAULT NOW()
      );
    2. Run the SQL statement. You can customize this table by adding or removing columns (like width, height, tags, etc.) based on the information you need from ImageKit and your application's requirements.
  4. Upload files to ImageKit.io and store metadata in Neon

    The recommended approach for client-side uploads is to generate secure authentication parameters on your backend. The client (for example, a web browser) uses these parameters, along with your public API key, to upload the file directly to ImageKit's Upload API. After a successful upload, the client sends the returned metadata (like fileId and url) back to your backend to be saved in Lakebase Postgres.

    This requires two backend endpoints:

    1. /generate-auth-params: Generates temporary authentication parameters (token, expire, signature).
    2. /save-metadata: Receives file metadata from the client after a successful upload to ImageKit and saves it to the Neon database.

    We'll use Hono for the server, imagekit for ImageKit interaction, and @neondatabase/serverless for Neon.

    First, install the necessary dependencies:

    Bash
    npm install imagekit @neondatabase/serverless @hono/node-server hono dotenv

    Create a .env file with your credentials:

    Bash
    # ImageKit.io Credentials
    IMAGEKIT_PUBLIC_KEY=your_imagekit_public_key
    IMAGEKIT_PRIVATE_KEY=your_imagekit_private_key
    IMAGEKIT_URL_ENDPOINT=your_imagekit_url_endpoint
    
    # Neon Connection String
    DATABASE_URL=your_neon_database_connection_string

    The following code snippet demonstrates this workflow:

    JavaScript
    import { serve } from '@hono/node-server';
    import { Hono } from 'hono';
    import ImageKit from 'imagekit';
    import { neon } from '@neondatabase/serverless';
    import 'dotenv/config';
    
    const imagekit = new ImageKit({
      publicKey: process.env.IMAGEKIT_PUBLIC_KEY,
      privateKey: process.env.IMAGEKIT_PRIVATE_KEY,
      urlEndpoint: process.env.IMAGEKIT_URL_ENDPOINT,
    });
    
    const sql = neon(process.env.DATABASE_URL);
    const app = new Hono();
    
    // Replace this with your actual user authentication logic
    const authMiddleware = async (c, next) => {
      // Example: Validate JWT, session, etc. and set user ID
      c.set('userId', 'user_123'); // Static ID for demonstration
      await next();
    };
    
    // 1. Generate authentication parameters for client-side upload
    app.get('/generate-auth-params', authMiddleware, (c) => {
      try {
        const authParams = imagekit.getAuthenticationParameters();
        // These params (token, expire, signature) are sent to the client
        // The client uses these + public key to upload directly to ImageKit
        return c.json({ success: true, ...authParams });
      } catch (error) {
        console.error('Auth Param Generation Error:', error);
        return c.json({ success: false, error: 'Failed to generate auth params' }, 500);
      }
    });
    
    // 2. Save metadata after client confirms successful upload to ImageKit
    app.post('/save-metadata', authMiddleware, async (c) => {
      try {
        const userId = c.get('userId');
        // Client sends metadata received from ImageKit after upload
        const { fileId, url } = await c.req.json();
    
        if (!fileId || !url) {
          throw new Error('fileId and url are required from ImageKit response');
        }
    
        // Insert metadata into Neon database
        await sql`
          INSERT INTO imagekit_files (file_id, file_url, user_id)
          VALUES (${fileId}, ${url}, ${userId})
        `;
    
        console.log(`Metadata saved for ImageKit file: ${fileId}`);
        return c.json({ success: true });
      } catch (error) {
        console.error('Metadata Save Error:', error.message);
        return c.json({ success: false, error: 'Failed to save metadata' }, 500);
      }
    });
    
    const port = 3000;
    serve({ fetch: app.fetch, port }, (info) => {
      console.log(`Server running at http://localhost:${info.port}`);
    });

    Explanation

    1. Setup: Initializes the Lakebase Postgres database client (sql), the Hono web framework (app), and the ImageKit Node.js SDK (imagekit) using credentials from environment variables.
    2. Authentication: Includes a placeholder authMiddleware. Replace this with your actual user authentication logic to ensure only authenticated users can generate upload parameters and save metadata.
    3. API endpoints:
      • /generate-auth-params (GET): Uses the ImageKit SDK's getAuthenticationParameters() method to create a short-lived token, expire timestamp, and signature. These are returned to the client.
      • /save-metadata (POST): This endpoint is called by the client after it has successfully uploaded a file directly to ImageKit's Upload API. The client sends the relevant metadata returned by ImageKit (like fileId, url, thumbnailUrl, etc.). The endpoint then inserts this metadata, along with the authenticated userId, into the imagekit_files table in Lakebase Postgres.

    We'll use Flask, imagekitio (ImageKit Python SDK), and psycopg2.

    First, install the necessary dependencies:

    Bash
    pip install Flask imagekitio psycopg2-binary python-dotenv

    Create a .env file with your credentials:

    env
    # ImageKit.io Credentials
    IMAGEKIT_PUBLIC_KEY=your_imagekit_public_key
    IMAGEKIT_PRIVATE_KEY=your_imagekit_private_key
    IMAGEKIT_URL_ENDPOINT=your_imagekit_url_endpoint # for example, https://ik.imagekit.io/your_instance_id
    
    # Neon Connection String
    DATABASE_URL=your_neon_database_connection_string

    The following code snippet demonstrates this workflow:

    Python
    import os
    
    import psycopg2
    from dotenv import load_dotenv
    from flask import Flask, jsonify, request
    from imagekitio.client import ImageKit
    
    load_dotenv()
    
    imagekit = ImageKit(
        public_key=os.getenv("IMAGEKIT_PUBLIC_KEY"),
        private_key=os.getenv("IMAGEKIT_PRIVATE_KEY"),
        url_endpoint=os.getenv("IMAGEKIT_URL_ENDPOINT"),
    )
    
    app = Flask(__name__)
    
    # Use a global PostgreSQL connection pool in production instead of connecting per request
    def get_db_connection():
        return psycopg2.connect(os.getenv("DATABASE_URL"))
    
    # Replace this with your actual user authentication logic
    def get_authenticated_user_id(request):
        # Example: Validate Authorization header, session cookie, etc.
        return "user_123"  # Static ID for demonstration
    
    # 1. Generate authentication parameters for client-side upload
    @app.route("/generate-auth-params", methods=["GET"])
    def generate_auth_params_route():
        try:
            user_id = get_authenticated_user_id(request)
            if not user_id:
                return jsonify({"success": False, "error": "Unauthorized"}), 401
    
            # Generate token, expire timestamp, and signature
            auth_params = imagekit.get_authentication_parameters()
            return jsonify(
                {
                    "success": True,
                    "token": auth_params["token"],
                    "expire": auth_params["expire"],
                    "signature": auth_params["signature"]
                }
            ), 200
    
        except Exception as e:
            print(f"Auth Param Generation Error: {e}")
            return (
                jsonify({"success": False, "error": "Failed to generate auth params"}),
                500,
            )
    
    # 2. Save metadata after client confirms successful upload to ImageKit
    @app.route("/save-metadata", methods=["POST"])
    def save_metadata_route():
        conn = None
        cursor = None
        try:
            user_id = get_authenticated_user_id(request)
            if not user_id:
                return jsonify({"success": False, "error": "Unauthorized"}), 401
    
            data = request.get_json()
            file_id = data.get("fileId")
            url = data.get("url")
    
            if not file_id or not url:
                raise ValueError("fileId and url are required from ImageKit response")
    
            # Insert metadata into Neon database
            conn = get_db_connection()
            cursor = conn.cursor()
            cursor.execute(
                """
                INSERT INTO imagekit_files (file_id, file_url, user_id)
                VALUES (%s, %s, %s)
                """,
                (file_id, url, user_id),
            )
            conn.commit()
            print(f"Metadata saved for ImageKit file: {file_id}")
            return jsonify({"success": True}), 201
    
        except (psycopg2.Error, ValueError) as e:
            print(f"Metadata Save Error: {e}")
            return (
                jsonify({"success": False, "error": "Failed to save metadata"}),
                500,
            )
        except Exception as e:
            print(f"Unexpected Metadata Save Error: {e}")
            return jsonify({"success": False, "error": "Server error"}), 500
        finally:
            if cursor:
                cursor.close()
            if conn:
                conn.close()
    
    if __name__ == "__main__":
        app.run(port=3000, debug=True)

    Explanation

    1. Setup: Initializes the Flask web framework (app), the PostgreSQL client function (get_db_connection) for Lakebase Postgres, and the ImageKit Python SDK (imagekit) using environment variables.
    2. Authentication: Includes a placeholder get_authenticated_user_id function. Replace this with your actual user authentication logic.
    3. API endpoints:
      • /generate-auth-params (GET): Uses the ImageKit SDK's get_authentication_parameters() method to create token, expire, and signature. These are returned to the client, usually as JSON.
      • /save-metadata (POST): Called by the client after it has successfully uploaded a file directly to ImageKit. The client provides the metadata returned by ImageKit. The backend validates the required fields and inserts the data along with the userId into the imagekit_files table in Lakebase Postgres using psycopg2.
    4. Database Connection: The example shows creating a new connection per request. In production, use a global connection pool for better performance.
  5. Testing the upload workflow

    This workflow involves getting authentication parameters from your backend, using those parameters to upload the file directly to ImageKit via curl, and then notifying your backend to save the metadata.

    1. Get authentication parameters: Send a GET request to your backend's /generate-auth-params endpoint.
      Bash
      curl -X GET http://localhost:3000/generate-auth-params

      Expected response: A JSON object containing the necessary parameters. For example:

      JSON
      {
        "success": true,
        "token": "20xxxx-xxxx-xxxx-a350-a463b3dd544e",
        "expire": 1745435716,
        "signature": "ffxxxxxx5f19b6a22e2bd6bd90ae8a7db21"
      }
    2. Upload file directly to ImageKit: Use the parameters obtained in Step 1, your ImageKit Public Key, and the file path to send a POST request with multipart/form-data directly to the ImageKit Upload API.
      Bash
      curl -X POST https://upload.imagekit.io/api/v1/files/upload \
           -F "file=@/path/to/your/test-image.png" \
           -F "publicKey=<YOUR_IMAGEKIT_PUBLIC_KEY>" \
           -F "token=<TOKEN_FROM_STEP_1>" \
           -F "expire=<EXPIRE_FROM_STEP_1>" \
           -F "signature=<SIGNATURE_FROM_STEP_1>" \
           -F "fileName=test-image.png" \
           -F "useUniqueFileName=true"

      Expected response (from ImageKit): A successful upload returns a JSON object with details about the uploaded file. Note the fileId, url, etc.

      JSON
      {
          "fileId": "<YOUR_FILE_ID>",
          "name": "<YOUR_FILE_NAME>",
          "size": "<YOUR_FILE_SIZE>",
          "versionInfo": {
              "id": "<YOUR_FILE_ID>",
              "name": "Version 1"
          },
          "filePath": "<YOUR_FILE_PATH>",
          "url": "https://ik.imagekit.io/<YOUR_INSTANCE_ID>/<YOUR_FILE_PATH>",
          "fileType": "image",
          "height": <YOUR_FILE_HEIGHT>,
          "width": <YOUR_FILE_WIDTH>,
          "thumbnailUrl": "https://ik.imagekit.io/<YOUR_INSTANCE_ID>/tr:n-ik_ml_thumbnail/<YOUR_FILE_PATH>",
          "AITags": null
      }
    3. Save metadata: Send a POST request to your backend's /save-metadata endpoint, providing the key details (like fileId, url) received from ImageKit in Step 2.
      Bash
      curl -X POST http://localhost:3000/save-metadata \
           -H "Content-Type: application/json" \
           -d '{
                "fileId": "<FILE_ID_FROM_STEP_2>",
                "url": "<URL_FROM_STEP_2>"
              }'

      Expected response (from your backend):

      JSON
      { "success": true }

    Expected outcome:

    • The file is successfully uploaded to your ImageKit Media Library.
    • You can verify a new row corresponding to the uploaded file exists in your imagekit_files table in Lakebase Postgres.
  6. Accessing file metadata and files

    With metadata stored in Lakebase Postgres, your application can easily retrieve references to the media hosted on ImageKit.io.

    Query the imagekit_files table from your application's backend whenever you need to display or link to uploaded files.

    Example SQL query:

    Retrieve files associated with a specific user:

    SQL
    SELECT
        id,             -- Your database primary key
        file_id,        -- ImageKit File ID
        file_url,       -- Base ImageKit CDN URL for the file
        user_id,
        upload_timestamp
    FROM
        imagekit_files
    WHERE
        user_id = 'user_123'; -- Use the actual authenticated user ID

    Using the data:

    • The query returns rows containing the file metadata stored in Neon.
    • The file_url is the direct link to the file on ImageKit's CDN. You can use this directly in <img> tags, video players, or links.
    • ImageKit transformations: A key benefit of ImageKit is real-time manipulation. You can append transformation parameters directly to the file_url to resize, crop, format, or optimize the media on-the-fly. For example, file_url + '?tr=w-300,h-200' would resize an image to 300x200 pixels. Learn more on ImageKit transformation docs for possibilities.

    This pattern separates media storage, optimization, and delivery (handled by ImageKit.io) from structured metadata management (handled by Lakebase Postgres).

Join our Discord Server to ask questions or see what others are doing with Neon. For paid plan support options, see Support.

Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu