The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
NAME
    Plack::App::dbi2http - Export DBI database as HTTP API (Riap::HTTP)

VERSION
    This document describes version 0.07 of Plack::App::dbi2http (from Perl
    distribution Plack-App-dbi2http), released on 2017-07-11.

SYNOPSIS
    Prepare and edit a config file:

     % cp share/sample-config/dbi2http.conf.yaml ~
     % emacs ~/dbi2http.conf.yaml; # edit log path and supply DBI dsn/user/password

    Run service:

     % cd share/www
     % plackup dbi2http.psgi
     HTTP::Server::PSGI: Accepting connections at http://0:5000/

    From another console, access HTTP API via, e.g. curl:

     % curl http://localhost:5000/list_tables
     countries
     continents

     % curl http://localhost:5000/list_columns?table=countries
     id
     ind_name
     eng_name
     tags

     % curl 'http://localhost:5000/list_columns?table=countries&detail=1&-riap-fmt=json-pretty'
     [
        200,
       "OK",
       [
          {
             "pos" : 1,
             "name" : "id",
             "type" : "text"
          },
          {
             "name" : "ind_name",
             "pos" : 2,
             "type" : "text"
          },
          {
             "type" : "text",
             "name" : "eng_name",
             "pos" : 3
          },
          {
             "pos" : 4,
             "name" : "tags",
             "type" : "text"
          }
       ]
     ]

     % curl 'http://localhost:5000/list_rows?table=countries'
     China   cn      Cina    panda
     Indonesia       id      Indonesia       bali,tropical
     Singapore       sg      Singapura       tropical
     United States of America        us      Amerika Serikat

     % curl 'http://localhost:5000/list_rows?table=countries&-riap-fmt=text-pretty'
     .-----------------------------------------------------------------.
     | eng_name                   id   ind_name          tags          |
     |                                                                 |
     | China                      cn   Cina              panda         |
     | Indonesia                  id   Indonesia         bali,tropical |
     | Singapore                  sg   Singapura         tropical      |
     | United States of America   us   Amerika Serikat                 |
     `-----------------------------------------------------------------'

    Or use App::riap, a client shell for Riap (with filesystem-like API
    browsing and shell tab completion):

     % riap http://localhost:5000/
     riap /> ls
     list_columns
     list_rows
     list_tables

     riap /> list_tables
     countries
     continents

     riap /> list_columns --table countries --detail

     riap /> list_rows --table countries

DESCRIPTION
    This module provides a sample Plack application, which you can
    customize, to export a DBI database as a HTTP API service.

    I was reading Yanick's blog entry today,
    <http://techblog.babyl.ca/entry/waack>, titled *Instant REST API for Any
    Databases* and I thought I'd quickly cobble up something similar using a
    different toolbox. Granted, the resulting HTTP API is not REST (read:
    it's better :-) and at 0.01 the API functions are somewhat limited
    (DBIx::FunctionalAPI) but this demonstrates how easy it is to create
    something usable.

    The tools and frameworks are: DBIx::FunctionalAPI which provides a set
    of functions: "list_tables", "list_columns", "list_rows",
    "create_table", "create_row", "rename_table", etc. These are normal Perl
    functions that accept "dbh", "table" arguments and so on.

    Next we have Perinci::Access::HTTP::Server, a set of Plack middlewares
    that let you access Perl functions over HTTP using the Riap::HTTP
    protocol. We compose the middlewares in a PSGI application called
    "dbi2http.psgi".

    All you need to do now is just run the PSGI application with Plack,
    using one of the many available PSGI servers. There is a configuration
    file required, to be put in the home directory, and can be copied from
    the provided sample config. All you need to set is basically just path
    to log file and the DBI connection information (db_dsn, db_user,
    db_password) and you're good to go.

    After the PSGI application is running, you can connect using a plain
    HTTP client like curl. Riap::HTTP exposes Perl modules and functions
    directly as URL paths. For example, you can just request the root URL
    first, and a help message is returned:

     % curl http://localhost:5000/
     function        list_columns
     function        list_rows
     function        list_tables

     Tips:
     * To call a function, try:
         http://localhost:5000/api/list_tables
     * Function arguments can be given via GET/POST params or JSON hash in req body
     * To find out which arguments a function supports, try:
         http://localhost:5000/api/list_tables?-riap-action=meta
     * To find out all available actions on an entity, try:
         http://localhost:5000/api/list_columns?-riap-action=actions
     * This server uses Riap protocol for great autodiscoverability, for more info:
         https://metacpan.org/module/Riap

    We can see there are 3 top-level functions available. Let's call the
    "list_tables" function to, well, list available tables.

     % curl http://localhost:5000/list_tables
     "main"."continents"
     "main"."countries"
     "main"."sqlite_master"
     "temp"."sqlite_temp_master"
     "main"."continents"
     "main"."countries"

    Functions usually return data structure and the PSGI application formats
    it as a presentable text to the client. To get the raw data, use the
    "-riap-fmt" special argument:

     % curl http://localhost:5000/list_tables?-riap-fmt=json-pretty
     [
       200,
       "OK",
       [
          "\"main\".\"continents\"",
          "\"main\".\"countries\"",
          "\"main\".\"sqlite_master\"",
          "\"temp\".\"sqlite_temp_master\"",
          "\"main\".\"continents\"",
          "\"main\".\"countries\""
       ]
     ]

    Actually, Riap provides more than just RPC (function call). It can also
    expose function metadata so the protocol is super-self-discoverable. For
    example, instead of the default "call" action, let's call the "meta"
    action to request the function metadata:

     % curl 'http://localhost:5000/list_tables?-riap-action=meta&-riap-fmt=json-pretty'
     [
       200,
       "OK (meta action)",
       {
          "entity_v" : "0.01",
          "result_naked" : "0",
          "entity_date" : "2014-06-15",
          "features" : {},
          "summary": "List available tables",
          "x.perinci.sub.wrapper.logs" : [
             {
                "normalize_schema" : "1",
                "validate_args" : "1",
                "validate_result" : "1"
             }
          ],
          "args_as" : "hash",
          "v" : "1.1",
          "args" : {}
       }
    ]

    Let's check the metadata of another function:

     % curl 'http://localhost:5000/list_columns?-riap-action=meta&-riap-fmt=json-pretty'
     [
       200,
       "OK (meta action)",
       {
          "summary": "List columns of a table",
          "args" : {
             "detail" : {
                "summary" : "Whether to return detailed records instead of just items/strings",
                "schema" : [
                   "bool",
                   {},
                   {}
                ]
             },
             "table" : {
                "schema" : [
                   "str",
                   {
                      "req" : 1
                   },
                   {}
                ],
                "req" : 1,
                "summary" : "Table name"
             }
          },
          "args_as" : "hash",
          "v" : 1.1,
          "result_naked" : 0,
          "x.perinci.sub.wrapper.logs" : [
             {
                "validate_args" : "1",
                "validate_result" : "1",
                "normalize_schema" : "1"
             }
          ],
          "features" : {},
          "entity_date" : "2014-06-15",
          "entity_v" : "0.01"
       }
     ]

    We can see from the above that the "list_columns" function accepts
    arguments "table" (a string, required) and "detail" (bool).

    For the full specification of the metadata format, see Rinci.

    Aside from using a low-level HTTP client, we can also use App::riap, a
    Riap client (just to note that Riap can also be accessed via transport
    protocol other than HTTP, but it's another subject matter). The client
    is a command-line shell with some conveniences like filesystem-like
    browsing of API tree, tab completion, debugging, command history, and
    others. Let's install the client and try it out:

     % cpanm -n App::riap
     % riap http://localhost:5000/
     riap />

    We are first presented with a prompt. Let's try listing what's available
    in the top-level directory:

     riap /> ls -l
     .-------------------------.
     | type       uri          |
     |                         |
     | function   list_columns |
     | function   list_rows    |
     | function   list_tables  |
     `-------------------------'

    We see that there are three functions available. To call a function, you
    can run it like a running a program:

     riap /> list_tables
     .-----------------------------------------------------------.
     | "main"."continents"           "temp"."sqlite_temp_master" |
     | "main"."countries"            "main"."continents"         |
     | "main"."sqlite_master"        "main"."countries"          |
     `-----------------------------------------------------------'

    Note that you can do completion on the command-line. You can even call
    with "--help" option like a normal program:

     riap /> list_columns --help
     Usage
       /list_columns --help (or -h, -?) [--verbose]
       /list_columns [options]
     Options
       --[no]detail
         Whether to return detailed records instead of just items/strings.
       --help, -h, -?
         Display this help message.
       --table=s [str] (required)
         Table name.

    This help message is generated from the metadata (so in the background,
    the client performs a "meta" request and converts it to a formatted help
    message).

    To add HTTP authentication (or do any other customization), you can just
    add a Plack middleware to the PSGI application.

    Last word, exporting a database as a public API service is usually not a
    good idea. In case you don't realize that ;-)

HOMEPAGE
    Please visit the project's homepage at
    <https://metacpan.org/release/Plack-App-dbi2http>.

SOURCE
    Source repository is at
    <https://github.com/perlancar/perl-Plack-App-dbi2http>.

BUGS
    Please report any bugs or feature requests on the bugtracker website
    <https://rt.cpan.org/Public/Dist/Display.html?Name=Plack-App-dbi2http>

    When submitting a bug or request, please include a test-file or a patch
    to an existing test-file that illustrates the bug or desired feature.

SEE ALSO
    Rinci, Riap, Riap::HTTP, DBIx::FunctionalAPI, App::riap

AUTHOR
    perlancar <perlancar@cpan.org>

COPYRIGHT AND LICENSE
    This software is copyright (c) 2017, 2015, 2014 by perlancar@cpan.org.

    This is free software; you can redistribute it and/or modify it under
    the same terms as the Perl 5 programming language system itself.