Providing images for <img>



In this how-to, you will learn how to create an endpoint for providing images to HTML <img> tags without client side JavaScript. In fact, the presented technique is suitable for providing not only images, but arbitrary files.

We will start with a minimal example that highlights the general concept. Afterwards we present a more detailed solution that fixes a few shortcomings of the first approach.


Be careful when saving binaries in the database, having a separate storage service for these is preferable in most cases. See Storing Binary files in the Database.

Minimal Example

First, we need a public table for storing the files.

create table files(
  id   int primary key
, blob bytea

Let’s assume this table contains an image of two cute kittens with id 42. We can retrieve this image in binary format from our PostgREST API by requesting /files?select=blob&id=eq.42 with the Accept: application/octet-stream header. Unfortunately, putting the URL into the src of an <img> tag will not work. That’s because browsers do not send the required Accept: application/octet-stream header.

Luckily we can specify the accepted media types in the raw-media-types configuration variable. In this case, the Accept: image/webp header is sent by many web browsers by default, so let’s add it to the configuration variable, like this: raw-media-types="image/webp". Now, the image will be displayed in the HTML page:

<img src="http://localhost:3000/files?select=blob&id=eq.42" alt="Cute Kittens"/>

Improved Version

The basic solution has some shortcomings:

  1. The response Content-Type header is set to image/webp. This might be a problem if you want to specify a different format for the file.

  2. Download requests (e.g. Right Click -> Save Image As) to /files?select=blob&id=eq.42 will propose files as filename. This might confuse users.

  3. Requests to the binary endpoint are not cached. This will cause unnecessary load on the database.

The following improved version addresses these problems. First, in addition to the minimal example, we need to store the media types and names of our files in the database.

alter table files
  add column type text,
  add column name text;

Next, we set up an RPC endpoint that sets the content type and filename. We use this opportunity to configure some basic, client-side caching. For production, you probably want to configure additional caches, e.g. on the reverse proxy.

create function file(id int) returns bytea as
  declare headers text;
  declare blob bytea;
    select format(
      '[{"Content-Type": "%s"},'
       '{"Content-Disposition": "inline; filename=\"%s\""},'
       '{"Cache-Control": "max-age=259200"}]'
      , files.type,
    from files where = into headers;
    perform set_config('response.headers', headers, true);
    select files.blob from files where = into blob;
    if found
    then return(blob);
    else raise sqlstate 'PT404' using
      message = 'NOT FOUND',
      detail = 'File not found',
      hint = format('%s seems to be an invalid file id',;
    end if;
$$ language plpgsql;

With this, we can obtain the cat image from /rpc/file?id=42. Thus, the resulting HTML will be:

<img src="http://localhost:3000/rpc/file?id=42" alt="Cute Kittens"/>