codomari · elixir · phoenix · couchdb · couchbeam · bootstrap

Codomari #1 (bootstraping)

As a language agnostic person who knows multiple human languages (Azerbaijani, Turkish, Ukrainian, Russian, English, German, a bit of Portuguese) and programming languages (PHP, JS, Ruby, Python, Go, Dart, Elixir) this time I am picking Elixir as language/stack which makes my brain and soul meditate during coding.

I truly enjoy Ruby kind of language features, but(!) I like Elixir way of writing functional code, where you can overload your methods for different kinds of data and shape and based on that can build crazy function piping combinations.
Also immutability of inputs at end of function execution tells me that I should not worry about side effects cause there is no shared state.

As web framework gold standard Phoenix framework.
It’s Ruby on Rails of Elixir, it’s mature, it’s well documented, it has great community.

As database I’m going to use CouchDB.

Reasons for choosing CouchDB this posts can tell everything:
Why CouchDB?
IBM - What is CouchDB?

My opinion about CouchDB is - it provides simple GET/SET methods to work with, provides easy to use administration methods about replication, keeps data change history (revisions on documents), provides attachments feature where You can have kinda User document and profileImage as attachment which adds extra W in comparison with S3.
It gives me to at DB as place where I store/retrieve data, manipulating and doing business logic on Application level.
So I don’t need to do complex sql queries, I can prepare the data which needed - I can simply subscribe to change feed and calculate data as extra app process and update related document(s), it gives me ability to not overload DB with extra responsibility which needed during joins, groupings and etc.

Also I’ll look like idiot and crazy but I want challenges! (:
I want some unique solution and write about it my blog.

As a console gamer I always try to pick Hard difficulty modes to enjoy the process.
It’s like going custom mode of difficulty and dragging enemy AI to maximum, enemy life maximum, own life to minimum.


Come on! It will be fun!


So here goes the process!

1. DataBase

Picked OS version and downloaded CouchDB from here

Since I’m using MacOS it will be installed as drag-dropping application to Applications folder.

Run the app and login to Admin panel.


Create database: create database


Create user (based on manual let’s create a document in _users database):

{
  "_id": "org.couchdb.user:codomari",
  "name": "codomari", 
  "password": "password to be hashed", 
  "roles": [],
  "type": "user"
}

create user

and it hashes our password automatically: created user

to verify created db user we can send curl request:

┌[num8er☮g8way1.local]-(~)
└> curl -v -X POST http://localhost:5984/_session -d 'name=codomari&password=codomari'
Note: Unnecessary use of -X or --request, POST is already inferred.
* Host localhost:5984 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:5984...
* connect to ::1 port 5984 from ::1 port 50435 failed: Connection refused
*   Trying 127.0.0.1:5984...
* Connected to localhost (127.0.0.1) port 5984
> POST /_session HTTP/1.1
> Host: localhost:5984
> User-Agent: curl/8.7.1
> Accept: */*
> Content-Length: 31
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 31 bytes
< HTTP/1.1 200 OK
< Cache-Control: must-revalidate
< Content-Length: 41
< Content-Type: application/json
< Date: Thu, 14 Nov 2024 13:46:25 GMT
< Server: CouchDB/3.4.2 (Erlang OTP/25)
< Set-Cookie: AuthSession=Y29kb21hcmk6NjczNUZGMzI63G6Lh6vQ5aVy-YbiXDB6FjMqu3iC_qpBDnb1SVp6778; Version=1; Expires=Thu, 14-Nov-2024 13:56:26 GMT; Max-Age=600; Path=/; HttpOnly
<
{"ok":true,"name":"codomari","roles":[]}

keep in mind with empty roles field can keep any string values except _admin, to create admin You’ve to configure system files by manual.
This means our codomari user cannot access database admin ui, you’ll see empty interface like this: admin ui without admin role


Add db admin and member users: add users to db

Based on official manual here about creating database and first document we have to put our data as document to same same database.
This means our data must have “type” field to distinguish type of entity.
Example:

{
    "_id": "user:3c3359bb-27e4-464a-bb4e-045673da769b",
    "type": "User",
    "credentials": {"username": "num8er", "password": "hashed-password-here"},
    "personal": {
      "dateOfBirth": {"year": 1988, "month": 1, "day": 8},
      "name": ["Anar", "K", "Jafarov"]
    }
}

new document

as a result we will get document with revision field:

saved document

which changes when we change data in document:

changed document


Let’s check if our codomari user can access db:

┌[num8er☮g8way1.local]-(~)
└> curl -u codomari:codomari http://localhost:5984/codomari/_all_docs
{"total_rows":1,"offset":0,"rows":[
{"id":"user:3c3359bb-27e4-464a-bb4e-045673da769b","key":"user:3c3359bb-27e4-464a-bb4e-045673da769b","value":{"rev":"4-076f6d48ae9cae9a2ea018eca46538f8"}}
]}

For now temporarily we are done with db.

Need to solve connectivity and structure our code.

2. Bootstrapping our API

As our app will be done using Elixir language stack after installing it, we need to install Phoenix framework before creating Phoenix app:

Step 1: install Hex

mix local.hex
┌[num8er☮g8way1.local]-(~/My/Codomari)-
└> mix local.hex
* creating /Users/num8er/.mix/archives/hex-2.1.1

Step 2: install Phoenix application generator

mix archive.install hex phx_new
[num8er☮g8way1.local]-(~/My/Codomari)-
└> mix archive.install hex phx_new

Resolving Hex dependencies...
Resolution completed in 0.006s
New:
  phx_new 1.7.14
* Getting phx_new (Hex package)
All dependencies are up to date
Compiling 11 files (.ex)
Generated phx_new app
Generated archive "phx_new-1.7.14.ez" with MIX_ENV=prod
* creating /Users/num8er/.mix/archives/phx_new-1.7.14

Step 3: create Phoenix app

Let’s create Phoenix app with minimum stuff.
So based on docs:

mix phx.new codomari_backend --no-ecto --no-assets --no-live
┌[num8er☮g8way1.local]-(~/My/Codomari)
└> mix phx.new codomari_backend --no-ecto --no-assets --no-live
* creating codomari_backend/lib/codomari_backend/application.ex
* creating codomari_backend/lib/codomari_backend.ex
* creating codomari_backend/lib/codomari_backend_web/controllers/error_json.ex
* creating codomari_backend/lib/codomari_backend_web/endpoint.ex
* creating codomari_backend/lib/codomari_backend_web/router.ex
* creating codomari_backend/lib/codomari_backend_web/telemetry.ex
* creating codomari_backend/lib/codomari_backend_web.ex
* creating codomari_backend/mix.exs
* creating codomari_backend/README.md
* creating codomari_backend/.formatter.exs
* creating codomari_backend/.gitignore
* creating codomari_backend/test/support/conn_case.ex
* creating codomari_backend/test/test_helper.exs
* creating codomari_backend/test/codomari_backend_web/controllers/error_json_test.exs
* creating codomari_backend/lib/codomari_backend_web/controllers/error_html.ex
* creating codomari_backend/test/codomari_backend_web/controllers/error_html_test.exs
* creating codomari_backend/lib/codomari_backend_web/components/core_components.ex
* creating codomari_backend/lib/codomari_backend_web/controllers/page_controller.ex
* creating codomari_backend/lib/codomari_backend_web/controllers/page_html.ex
* creating codomari_backend/lib/codomari_backend_web/controllers/page_html/home.html.heex
* creating codomari_backend/test/codomari_backend_web/controllers/page_controller_test.exs
* creating codomari_backend/lib/codomari_backend_web/components/layouts/root.html.heex
* creating codomari_backend/lib/codomari_backend_web/components/layouts/app.html.heex
* creating codomari_backend/lib/codomari_backend_web/components/layouts.ex
* creating codomari_backend/priv/static/images/logo.svg
* creating codomari_backend/lib/codomari_backend/mailer.ex
* creating codomari_backend/lib/codomari_backend_web/gettext.ex
* creating codomari_backend/priv/gettext/en/LC_MESSAGES/errors.po
* creating codomari_backend/priv/gettext/errors.pot
* creating codomari_backend/priv/static/robots.txt
* creating codomari_backend/priv/static/favicon.ico
* creating codomari_backend/priv/static/assets/app.js
* creating codomari_backend/priv/static/assets/app.css
* creating codomari_backend/priv/static/assets/home.css

Fetch and install dependencies? [Yn] y
* running mix deps.get
* running mix deps.compile

We are almost there! The following steps are missing:

    $ cd codomari_backend

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server

--no-ecto - Ecto does have providers for CouchDB, but I’m planning to work with db directly using couchbeam and calling it’s methods through DAO-s which return Entities. Couchbeam actively supported (yes, I did checked different couchdb libraries for Elixir, but all of them not updated during last 2 years) is reason why I picked it.
--no-live - we are going to make http api and not providing liveview stuff which renders veiws and data change in “realtime”.
--no-assets - same reason as with --no-live flag.

Step 4: check created app

cd codomari_backend
mix phx.server
┌[num8er☮g8way1.local]-(~/My/Codomari)
└> cd codomari_backend
┌[num8er☮g8way1.local]-(~/My/Codomari/codomari_backend)-[git://main ✔]-
└> mix phx.server
Compiling 14 files (.ex)
warning: defining a Gettext backend by calling

    use Gettext, otp_app: ...

is deprecated. To define a backend, call:

    use Gettext.Backend, otp_app: :my_app

Then, instead of importing your backend, call this in your module:

    use Gettext, backend: MyApp.Gettext

  lib/codomari_backend_web/gettext.ex:23: CodomariBackendWeb.Gettext (module)

Generated codomari_backend app
[info] Running CodomariBackendWeb.Endpoint with cowboy 2.12.0 at 127.0.0.1:4000 (http)
[info] Access CodomariBackendWeb.Endpoint at http://localhost:4000

as result in browser we will see such screen:

phoenix welcome page


For now minimum necessary stuff is done.
In next iteration going to integrate CouchDB client and do few endpoints to check.


Talk Soon!