Free Pascal Compiler and Apache CouchDB REST API

CouchDB is a NoSQL document store. This database is highly concurrent, can run stand alone and makes use of a REST API with JSON.

CouchDB needs only a HTTP client for CRUD (Create, Read, Update and Delete) operations. This makes working with CouchDB simple in most languages.

Lets setup our program and create an HTTP client in Pascal. We will need the ability to create and parse JSON. Default security policy is local connections on port 5984 or 'http://localhost:5984' the default server address.

program CouchDB;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}
  Cthreads, Cmem,
  {$ENDIF}
  Classes, SysUtils, FPHTTPClient, OpenSSLSockets, FPJSON, JSONParser;

const
  CouchDBAddress = 'http://localhost:5984/';
  DatabaseName = 'fpctest';
  DesignDocName = 'fpc';
  ViewName = 'article';

var
  SessionDoc, ViewDesignDoc, ArticleDoc, Rows, Items, Article, Rev: TJSONObject;
  Values: TJSONArray;
  DBResponse, JavaScriptMapFunction, Session, DocID: String;
  Parser: TJSONParser;
  I: Integer;
begin
  with TFPHTTPClient.Create(nil) do
  try
    { Session }

    { Create database }

    { Insert named design document }

    { Insert document }

    { Query document }

    { Delete document }

    { Delete database }

  finally
    { Free }
    Free;
  end;
end.

{ Session }


Session endpoint : POST : http://localhost:5984/_session

Set CouchDB credentials in Environment Variables
#!/usr/bin/env bash

export CDBAdmin=""
export CDBPassword=""

JSON request body (Credentials)
{
  "name" : "",
  "password" : ""
}

SessionDoc := TJSONObject.Create;
SessionDoc.Add('name', GetEnvironmentVariable('CDBAdmin'));
SessionDoc.Add('password', GetEnvironmentVariable('CDBPassword'));

AddHeader('Content-Type', 'application/json; charset=UTF-8');
RequestBody := TStringStream.Create(SessionDoc.AsJSON);
Post(CouchDBAddress + '_session');
RequestBody.Free;
Session := Cookies.Values['AuthSession'];

{ Create database }


New database endpoint : PUT : http://localhost:5984/fpctest

Put(CouchDBAddress + DatabaseName);

{ Insert named design document }


Named design document endpoint : PUT : http://localhost:5984/fpctest/_design/fpc

JSON request body (Design Document)
{
  "_id" : "_design/fpc",
  "indexes" : {},
  "lists" : {},
  "views" : {
    "article" : {
      "map" : "function (doc) { if(doc.kind == \"article\") { emit(doc.kind, { \"headline\": doc.headline, \"body\": doc.body, \"published\": doc.published, \"author\": doc.author })}}"
    }
  },
  "shows" : {},
  "language" : "javascript"
}

JavaScriptMapFunction := 'function (doc) { '+
                                 'if(doc.kind == "article") { '+
                                   'emit(doc.kind, { '+
                                     '"headline": doc.headline, '+
                                     '"body": doc.body, '+
                                     '"published": doc.published, '+
                                     '"author": doc.author '+
                                   '})'+
                                 '}'+
                               '}';
    
ViewDesignDoc := TJSONObject.Create;
ViewDesignDoc.Add('_id', '_design/'+DesignDocName);
ViewDesignDoc.Add('indexes', TJSONObject.Create);
ViewDesignDoc.Add('lists', TJSONObject.Create);
ViewDesignDoc.Add('views', TJSONObject.Create(
  [ViewName, TJSONObject.Create(
  ['map', JavaScriptMapFunction])]));
ViewDesignDoc.Add('shows', TJSONObject.Create);
ViewDesignDoc.Add('language', 'javascript');

RequestHeaders.Clear;
AddHeader('X-CouchDB-WWW-Authenticate', 'Cookie');
AddHeader('Cookie', 'AuthSession=' + Session);
AddHeader('Content-Type', 'application/json; charset=UTF-8');
RequestBody := TStringStream.Create(ViewDesignDoc.AsJSON);
Put(CouchDBAddress + DatabaseName + '/_design/'+DesignDocName);
RequestBody.Free;

{ Insert document }


Insert document endpoint : POST : http://localhost:5984/fpctest

JSON request body (Article)
{
  "kind" : "article",
  "headline" : "A Test Headline",
  "body" : "This would be an article body.",
  "published" : "4/20/2020",
  "author" : "John Horst"
}

ArticleDoc := TJSONObject.Create;
ArticleDoc.Add('kind', 'article');
ArticleDoc.Add('headline', 'A Test Headline');
ArticleDoc.Add('body', 'This would be an article body.');
ArticleDoc.Add('published', 'article');
ArticleDoc.Add('author', 'John Horst');

RequestHeaders.Clear;
AddHeader('X-CouchDB-WWW-Authenticate', 'Cookie');
AddHeader('Cookie', 'AuthSession=' + Session);
AddHeader('Content-Type', 'application/json; charset=UTF-8');
RequestBody := TStringStream.Create(ArticleDoc.AsJSON);
Post(CouchDBAddress + DatabaseName);
RequestBody.Free;

{ Query document }


Query document endpoint : GET : http://localhost:5984/fpctest/_design/fpc/_view/article

DBResponse := Get(CouchDBAddress + DatabaseName + '/_design/'+ DesignDocName + '/_view/' + ViewName);

Parser := TJSONParser.Create(DBResponse);
Rows := Parser.Parse as TJSONObject;
Values := Rows.Arrays['rows'];

for I := 0 to Values.Count -1  do
begin
  Items := Values.Objects[i];
  Article := Items.Objects['value'];
  WriteLn(Article.Strings['headline']);
  WriteLn(Article.Strings['body']);
  WriteLn(Article.Strings['published'] + ' by ' + Article.Strings['author']);
end;
RequestBody.Free;

{ Delete document }

Fetch document revision endpoint : GET : http://localhost:5984/fpctest/{docID}
Delete revision endpoint : DELETE : http://localhost:5984/fpctest/{docID}?rev={docRev}

DBResponse := Get(CouchDBAddress + DatabaseName + '/' + DocID);
Parser := TJSONParser.Create(DBResponse);
Rev := Parser.Parse as TJSONObject;
RequestBody.Free;
Delete(CouchDBAddress + DatabaseName + '/' + DocID + '?rev=' + Rev.FindPath('_rev').AsString);

{ Delete database }


Delete database endpoint : DELETE : http://localhost:5984/fpctest

Delete(CouchDBAddress + DatabaseName);

Comments

  1. Replies
    1. You're beautiful ya know. I'm motivated on lockdown to publish and job hunt mamas. I have a ring to buy and you better wear it.

      Delete

Post a Comment