Phoenix Framework Pt. 1
This article assumes that you already know Elixir programming language. Further, following are the software you have on your computer.
- Elixir 1.14 or later
- Erlang 24 or later.
- PostgreSQL
If you do not have Elixir and do not know Elixir programming language, please refer the official website to install the Elixir language on your computer and go through this very well written Getting Started docs on Elixir language. This Getting Started covers Elixir in detail and that is what you need before jumping into this series on Phoenix framework. I will explain some Elixir concepts but those will be brief and specific within the context of framework only.
If you do not have Phoenix framework on you computer, please follow this excellent Installation guide. Please install all the suggested software written on this Installation guide. Please also confirm the version of these software as stated above.
-–
Open terminal and run the following command.
mix phx.new helloThis command will scaffold the application with default files and
folders. Then it will ask you to install dependencies or not. Press Y or y and wait for the dependencies to be installed.
Once the dependencies are installed, you will get further
instructions on what to do next. For example, the first thing to do is
to cd into the created hello application/folder.
Next, open the application or hello folder into your favorite text-editor. For example, I am using Visual Studio Code.
code .Then as suggested in further instructions, open config/dev.exs file. In this file, we are going to adjust the database connection config. Following is a part of the code copied from the config/dev.exs file that we need to adjust for the same.
# Configure your database
config :hello, Hello.Repo,
username: "postgres",
password: "postgres",
hostname: "localhost",
database: "hello_dev",
stacktrace: true,
show_sensitive_data_on_connection_error: true,
pool_size: 10As the database is now configured, we can follow the command to create the database and associated tables in it.
mix ecto.createThe stage is set up. Run the application using the following command.
mix phx.serverThe application is up and running at http://localhost:4000. Open this URL in the web browser and you should see a landing page with text “Peace of mind from prototype to production.” I like this phrase!
Congratulations! You have just created an application in the Phoenix framework.
---
Hono Tutorial Pt. 3
Before we go further and learn more about the Hono framework,
the one thing that is bothering me is database credentials written in
code. We should adjust the existing code by removing the credentials
from the code and put it within the .env file. We should never hard-code the secret(s) as per 12factor app.
Go ahead and create a .env file in the root of the project.
touch .env
Let’s define the following environment variables within this created .env file.
DB_NAME="hono" DB_HOST="localhost" DB_PORT=5432 DB_USER="postgres" DB_PASS="password"
We should not commit this file in the Git. The file should be added within .gitignore file. But, to give and idea about the existence of the .env file, we should have the .env.example file in the repo with same content as we have in .env file but with dummy environment variables or env vars.
cp .env .env.example
Hono has an adapter to read the env vars from the .env file. But, unfortunately it is not working for the Node and outside of c or context. For now, we are going to use the dotenv package. Let’s first install the dotenv package.
npm i -E dotenv
Import dotenv/config in app.js and we are good to go.
import { serve } from "@hono/node-server"; import { Hono } from "hono"; import postgres from "postgres"; import "dotenv/config"; const sql = postgres({ host: "localhost", port: 5432, username: "postgres", password: "password", database: "hono", }); const app = new Hono(); app.get("/", async (c) => { const result = await sql`SELECT 1 + 1`; console.log(result); return c.text("Hello!"); }); serve(app, (info) => { console.log(`Listening on http://localhost:${info.port}`); });
Now, replace the hard-coded database connection options within app.js with the env vars using process.env as follows.
import { serve } from "@hono/node-server"; import { Hono } from "hono"; import postgres from "postgres"; import "dotenv/config"; const sql = postgres({ host: process.env.DB_HOST, port: process.env.DB_PORT, username: process.env.DB_USER, password: process.env.DB_PASS, database: process.env.DB_NAME, }); const app = new Hono(); app.get("/", async (c) => { const result = await sql`SELECT 1 + 1`; console.log(result); return c.text("Hello!"); }); serve(app, (info) => { console.log(`Listening on http://localhost:${info.port}`); });
With these changes, we have removed the hard-coded secrets from our code. These changes should not effect the output. Run the application to confirm the same!
npm run dev Listening on http://localhost:3000
Visit the http://localhost:3000 in the web browser and you should see the following output in terminal while displaying Hello! in the browser.
Result(1) [ { '?column?': 2 } ]
---
Hono Tutorial Pt. 2
In this article, we are going to connect the database in Hono application. For the purpose of demonstration, we are going to use PostgreSQL database and postgres as npm package. You are free to use any other package and database as steps will be similar except you need to consult the respected documentation for initial setup.
Let’s install a postgres package.
npm i -E postgres
As per postgres package documentation, we first need to import the postgres function. This function accepts the connection options, and we can save the connection within sql variable as follows.
import { serve } from "@hono/node-server"; import { Hono } from "hono"; import postgres from "postgres"; const sql = postgres({ host: "localhost", port: 5432, username: "postgres", password: "password", database: "hono", }); const app = new Hono(); app.get("/", async (c) => { return c.text("Hello!"); }); serve(app, (info) => { console.log(`Listening on http://localhost:${info.port}`); });
You need to adjust the connection options to the postgres() function based on your PostgreSQL setup. Once done, you now have the active connection within sql variable.
Let’s execute the simple SELECT 1 + 1
query within root route and print the result of it. The query returns
the promise, and due to this we need to update the signature of the
callback function to async/await.
import { serve } from "@hono/node-server"; import { Hono } from "hono"; import postgres from "postgres"; const sql = postgres({ host: "localhost", port: 5432, username: "postgres", password: "password", database: "hono", }); const app = new Hono(); app.get("/", async (c) => { const result = await sql`SELECT 1 + 1`; console.log(result); return c.text("Hello!"); }); serve(app, (info) => { console.log(`Listening on http://localhost:${info.port}`); });
That’s it! Run the application using the following command.
npm run dev
Visit http://localhost:3000 in the browser and check the terminal. You should see the following console message.
Result(1) [ { '?column?': 2 } ]
We got the result 2 as 1+1 and when you don’t define the column name in SELECT clause in PostgreSQL, PostgreSQL by default gives ?column? as column name.
With these changes, our Hono application is connected with database. You might say that this is boring and nothing specific to Hono. That is right! As Hono doesn’t re-invent wheel in every aspect of the web development and database connection is one such a case!
Note: The code is available at GitHub. Checkout chapter02 branch for the code of this chapter.
---
Phoenix Framework Pt. 3
The routes of the application are defined within the lib/hello_web/router.ex file. In this file, look for the following code snippet.
scope "/", HelloWeb do
pipe_through :browser
get "/", PageController, :home
endIn a future article, we will cover what other code does in this file.
But, for now we can focus on this code block and specifically, this
line - get "/", PageController, :home. It means when a GET / or root route is requested, execute the home action (function) of PageController.
You can find this PageController or page_controller.ex file within the lib/hello_web/controllers folder. Open this file, and you should see the following code.
defmodule HelloWeb.PageController do
use HelloWeb, :controller
def home(conn, _params) do
# The home page is often custom made,
# so skip the default app layout.
render(conn, :home, layout: false)
end
endThere we have the home function! This function renders home template or home.html.heex file without layout and located at lib/hello_web/controllers/page_html folder.
Don’t worry about the location of these files! Don’t memorize the locations. With some practice, you know all these eventually.
So, we have the router.ex
file that defines the routes of the application that point to the
controller’s action and the controller’s action to render a view. There
is one more file page_html.ex located at the lib/hello_web/controllers
folder. But, we haven’t talked about it yet and we’ll talk about it in
future articles. But, it is required to have to complete this
request-response cycle.
---
Let’s add a new route GET /about. Open router.ex file, and write the following.
scope "/", HelloWeb do
pipe_through :browser
get "/", PageController, :home
get "/about", PageController, :about
endWhen the GET /about is requested, we are going to call about the action of the PageController file. Let’s add this about function in the page_controller.ex file. Open the page_controller.ex file and write the following.
defmodule HelloWeb.PageController do
use HelloWeb, :controller
def home(conn, _params) do
# The home page is often custom made,
# so skip the default app layout.
render(conn, :home, layout: false)
end
def about(conn, _params) do
# The home page is often custom made,
# so skip the default app layout.
render(conn, :about)
end
endI almost copied the home function with two notable changes - we are going to render about.html.heex file and remove the layout argument to use default layout. We do not have this about.html.heex file in the lib/hello_web/controllers/page_html file. So, let’s create one.
touch lib/hello_web/controllers/page_html/about.html.heexOpen this about.html.heex file and write the following code.
<h1>About<h1>Visit http://localhost:4000/about file and you should see this header text with navigation. This navigation is coming from the default layout.
With these changes, now we have the Phoenix application with a new route!
---
Phoenix Framework Pt. 2
In this article, I am going to briefly explain to you the purpose of a few files and folders present in this hello
Phoenix project. This will be brief. Because, as you start to become
familiar with Phoenix projects, it will become second nature to you.
We have a _build
folder. When you run the application, Elixir compiles the project and
saves the artifacts within this folder. You can ignore what is within
this folder because Git also ignored this folder in the .gitignore file!
We have an assets
folder. You can put static assets such as JavaScript or CSS within this
folder that further require compilation such as TailwindCSS. But, if
you want to save the static assets that don’t require the compilation
such as plain CSS files or images, you can put it within the priv/static folder.
We have a config folder. This folder contains the configurations of the project. config.exs is the starting point and then we have files after each of the environments. We already know the config/dev.exs file as modified for the database configuration for the dev or development environment.
We have a deps
folder. This folder contains the dependencies of this project. Again,
we do not have to worry about this file as this file is ignored in .gitignore.
We have a lib folder. This folder contains the application code. We are going to spend most of the time in this folder.
We have a priv
folder. This folder contains files which are directly not needed in
production but the application depends on. For example, migration
scripts. We need migration scripts for database modification but
production does not directly include the code of it.
Finally, all the test cases are written within the test folder. And mix.exs and mix.lock file contains the dependencies of the project.
I think this brief overview is enough to get started and I will cover in details where it requires.
---
Let’s do a quick modification in scaffold code to print the hello,
world text instead of the default landing page. What you are seeing at http://localhost:4000 is coming from lib/hello_web/controllers/page_html/home.html.heex. Open this file and replace the whole HTML with the following.
<h1>hello, world</h1>You do not have to reload the page in the browser thanks to the Phoenix framework as it will auto reload for you! There you have the hello, world application in the Phoenix framework.
---
Migration
I'm migrating this blog to chauhankiran.pages.dev. This chauhankiran.blogspot.com blog is created in a first-generation blog theme that I highly customized. I want to customize more. But, I'm not getting any help on it. Also, I'm unable to find the documentation on first-generation themes. Current theme is too complicated for me! I remember copying one widget of 12-15 lines of code and expanding it into a couple of hundred lines of code is complicated for me.
Although, chauhankiran.pages.dev looks the same as this one, it is created in Jekyll and the theme is customized to retain the curren look-and-feel. The repo. is hosted on GitHub and deployed on CloudFlare pages.
I'm slowly migrating these articles. I do not plan to delete this current blogspot. But, the new content will be posted there!
---
Hono Tutorial Pt. 1
This is the first article in a series where we are going to build a simple back-end application in Hono in Node.js environment.
Create a new folder with the name hono-app.
mkdir hono-app
Go inside the created hono-app folder.
cd hono-app
Create a package.json file using npm with default values (-y).
npm init -y
Let’s install the hono package.
npm i -E hono
In order to use Hono in the Node.js environment, we also need to the @hono/node-server package.
npm i -E @hono/node-server
Finally, let’s install nodemon to ease the development. We should install this as a development dependency (-D).
npm i -E -D nodemon
For now, we are done with setup. Let’s move on to the coding side. Create a new file with name app.js.
touch app.js
Open this file (or hono-app folder) in your favorite text-editor/IDE and write the following code.
import { Hono } from 'hono'; const app = new Hono();
We are using the latest import...from syntax in our Node.js application! We have imported the Hono and then created an instance of the Hono() using a new keyword and saved it as an app.
Let’s now define a root route(/) and return a simple Hello! text as a response (don’t forget to return the response).
import { Hono } from 'hono'; const app = new Hono(); app.get('/', (c) => { return c.text('Hello!'); });
Here, c is a context and it contains both request and response in it with many useful methods such as .text(). The .text() method return response in plain text with string passed to it e.g. Hello!.
Now, to run the application in Node, we need to use @hono/node-server package. Import serve from @hono/node-server package.
import { Hono } from 'hono'; import { serve } from '@hono/node-server'; const app = new Hono(); app.get('/', (c) => { return c.text('Hello!'); });
Use this serve method as follows.
import { Hono } from 'hono'; import { serve } from '@hono/node-server'; const app = new Hono(); app.get('/', (c) => { return c.text('Hello!'); }); serve(app, (info) => { console.log(`Listening on http://localhost:${info.port}`); });
serve() function takes Hono instance as the first argument and second argument should be a callback function with info param that gives useful information such as port. If you don’t define the port value as we did, the default is 3000.
Before we go further and run the application, we need to do some adjustment in the package.json file. As we are using the latest import...from syntax, we need to define type as module. Also, update the main entry point to app.js file.
{
"name": "hono-app",
"version": "1.0.0",
"main": "app.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@hono/node-server": "1.14.0",
"hono": "4.7.5"
},
"devDependencies": {
"nodemon": "3.1.9"
}
}
Finally, let’s add two scripts dev and start to run the app.js file with nodemon and node respectively.
{
"name": "hono-app",
"version": "1.0.0",
"main": "app.js",
"type": "module",
"scripts": {
"dev": "nodemon app.js",
"start": "node app.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@hono/node-server": "1.14.0",
"hono": "4.7.5"
},
"devDependencies": {
"nodemon": "3.1.9"
}
}
With these changes, we are now ready to run the application. Run the following command in the terminal.
npm run dev Listening on http://localhost:3000
Open http://localhost:3000 in the web browser and you should see Hello! as the plan text. If that is the case, congratulations! You have just created first Hono application in Node.js.
Note: The code is available at GitHub. Checkout chapter01 branch for the code of this chapter.
---