Building an uptime monitor in Elixir and Phoenix: Project setup
Welcome to the first part of our Elixir/Phoenix tutorial series. In this one, you will learn how to build an uptime monitor for websites and web applications. Step one: setting up the project.
As an Elixir/Phoenix development company, we rarely get a better opportunity to showcase our command of both the language and framework than portfolio items and blog posts. But it’s not about bragging. Our point is to share knowledge with the web development community. If you are thinking about monitoring the uptime and response time of your web app, read on!
Elixir/Phoenix uptime monitor: General foundations
Technical stack and initial arrangements
In this series of articles, we are going to create an uptime monitor for web applications using Elixir and the Phoenix framework. In general, we want it to be simple, fast and scalable.
The tech stack we are going to use is:
- Elixir & Phoenix framework,
- PostgreSQL,
- Tailwind,
- JavaScript/jQuery.
Let’s name our project EMeter. We want it to be a tool where you put your website URL and it will gather its uptime and response time. The uptime will be shown with a given accuracy, depending on chosen timespan. The same goes for the response time. Although the project seems simple, it has some confusing parts. In our articles, we will firstly talk about possible ways of solving these problems, showing our way of reasoning, then we will pick the most suitable option and implement it.
The series agenda is listed below, although it is pretty flexible and some topics might need more than one article to be fully covered.
- Setting up the project - you are here right now.
- Authorization and authentication.
- Data models.
- Routing and controllers.
- Data gathering.
- Serving the data.
- Presentation/UI.
- The end?
Let’s jump into it! New to Elixir programming? Try our Free Elixir Course!
Elixir/Phoenix uptime monitor: Setting up the project
Firstly we need to create a project for our app. Let’s pick a matching name for it. As I mentioned before, we already have the name - EMeter. That is why I am going to write “e_meter” as the path parameter.
My personal advice for picking Elixir project names: don’t make them too long. At least for me, there is nothing more frustrating than typing the same phrase, especially a long one over and over again. Pick a single word or two, which suits the project. It also looks cleaner to me when creating aliases and imports later on.
mix phx.new e_meter --umbrella --no-ecto
We use the mix phx.new command, because we want our project to be pre-filled with the Phoenix framework dependencies. As you have noticed, we also use the --umbrella and --no-ecto flag. The first one generates an umbrella project, which basically means that there are two applications created - one for our domain, and the second one for the web interface.
The thing is that both of them will be run on a single Beam VM. You can also add more apps to the umbrella, which we are going to do later on. All of the umbrella apps can be treated as standalone applications unless they have in_umbrella: true dependencies within.
At Prograils, we agreed that umbrella apps are the way to go with a standard Phoenix project. We don’t use this flag only for tiny applications, i.e. mostly for proofs of concepts. That is because umbrella applications allow you to separate the responsibilities between the apps, which means complex systems are split into smaller parts. This makes code simpler than in regular apps and thus also easier to maintain. By extending the project setup time, we achieve long-term goals.
The second flag, which is --no-ecto, causes the Ecto files to not be generated in our project. Ecto is a database wrapper and we will discuss the way we handled the database connection later on.
While executing this command we will be asked if we want to fetch and install dependencies. That is not a tricky question, we will need to do it anyways. Just type “y” and wait for the installation to complete.
Adding jQuery to the project
Adding jQuery to the project is pretty straightforward. As we enter the e_meter_umbrellafolder we will have to go to the web application assets folder.
cd apps/emeterweb/assets/
Then we can install jQuery via the npm command:
npm install --save jquery
And the last thing we need to do is adding jQuery to the webpack.config.js in the plugins section. The file is located in the assets folder.
At the top of the file, add the line requiring webpack to be used:const webpack = require('webpack');
And then, in the plugins add a new list element, as listed below:
plugins: [
...,
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
]
From now on, we should be able to use jQuery in our JavaScript files.
Uptime monitor in Elixir/Phoenix: Adding Tailwind
At Prograils, we like good-looking things. That’s why we have chosen Tailwind. You can use Bootstrap as the alternative. It should not be a problem for you, because this series of articles is mostly about Elixir and Phoenix, not about frontend and styling.
To install Tailwind in our project, we need to go to the assets folder again and launch the npm installation command:
npm install tailwindcss@latest postcss@latest autoprefixer@latest postcss-loader@4.2 --save-dev
Also, in the assets, we create the postcss.config.js file with the content listed below. Basically, it will tell PostCSS to use Tailwind and Autoprefixer plugins.
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}
Then we add postcss-loader to our webpack.config.js. It should be located between the css-loader and sass-loader.
test: /\.[s]?css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader'
],
The next thing is launching the Tailwind initialization command, also in the assets folder:
npx tailwindcss init
The Tailwind init command should create a new file - tailwind.config.js in our assets folder. Now we want to open the file and replace the purge option. The purge option stands for reading which selectors from Tailwind are actually in use by our project and which ones should be removed from the generated css files. Underneath it uses the PurgeCSS library.
module.exports = {
purge: [
'../lib/**/*.ex',
'../lib/**/*.leex',
'../lib/**/*.eex',
'./js/**/*.js'
],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}```
For the purge option to work, we need one more thing - setting NODE_ENV to production after deployment. We can achieve this by changing the deploy script in package.json in the assets folder:
"deploy": "NODE_ENV=production webpack --mode production",
The last thing is to add the Tailwind styles to our app.scss file, which is pretty straightforward:
- @import "tailwindcss/base";
- @import "tailwindcss/components";
- @import "tailwindcss/utilities";
From now on we should be able to use Tailwind styles in our application.
Adding the database application
As mentioned before we like separating the responsibilities between the apps. To show this in action, we will create a separate application, whose responsibility is going to be to keep the database connection and to implement general database utilities, like e.g. example pagination. That is why we used --no-ecto option while initializing the application. If we didn’t - Ecto would be an EMeter application dependency. That is by no means a mistake, but for the reasons mentioned above, we like separating our repo from the domain application.
First things first - let’s create a new application in our apps folder.
cd apps
mix new postgres
It should generate a nice template for our Postgres application.
Now it is time to add the needed dependencies into apps/postgres/mix.exs. We will use this app for database operations - so our dependencies are only database-related.
defp deps do
[
{:ecto_sql, "~> 3.4"},
{:postgrex, ">= 0.0.0"},
{:jason, "~> 1.2"}
]
``` end
We also would like to add some basic aliases also in the mix.exs file, just to make life easier during the development:
defp aliases do
[
setup: ["deps.get"],
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]
]
end
Don’t forget to add aliases() to the project().
def project do
[
app: :postgres,
version: "0.1.0",
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
elixir: "~> 1.11",
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps()
]
end
From now on, the mix setup command from the main directory will also affect the Postgres app.
The next thing is to create a lib/postgres/repo.ex file with the Postgres.Repo module within, which defines a repository:
defmodule Postgres.Repo do
use Ecto.Repo,
otp_app: :postgres,
adapter: Ecto.Adapters.Postgres
end
Now let’s create a lib/postgres/application.ex file, where we will start our repo. The file uses the Application behavior, which defines the way of working with applications and their callbacks. In children, we specify only Postgres.Repo, so it will be started and supervised. ``` defmodule Postgres.Application do use Application def start(_type, _args) do children = [ Postgres.Repo ] Supervisor.start_link(children, strategy: :one_for_one, name: Postgres.Supervisor) end end ```
Finally, it is time to add the Postgres.Application module to the application() function in the apps/postgres/mix.exs file and we should be fine with our Postgres application.
def application do
[
mod: {Postgres.Application, []},
extra_applications: [:logger]
]
end
Also remember to add {:postgres, in_umbrella: true} as a dependency in the e_meter and e_meter_web mix files.
# Specifies your project dependencies.
#
# Type `mix help deps` for examples and options.
defp deps do
[
{:phoenix_pubsub, "~> 2.0"},
{:postgres, in_umbrella: true}
]
end
A nice addition to make up things before going further into configs is adding the ecto.setup and ecto.reset to our main directory mix.exs file. It will allow us to launch mix ecto.reset and mix ecto.setup from the main directory.
defp aliases do
[
# run `mix setup` in all child apps
setup: ["cmd mix setup"],
"ecto.setup": ["ecto.create", "ecto.migrate", "run apps/postgres/priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"]
]
end
At last, it is time to adjust config files - starting with the main config.exs. Here we have to configure the :postgres app with the ecto_repos option leading to the Postgres.Repo:
use Mix.Config
config :postgres,
ecto_repos: [Postgres.Repo]
config :e_meter_web,
generators: [context_app: :e_meter]
...
In the test.exs we add a test configuration for Postgres.Repo.
config :postgres, Postgres.Repo,
username: "postgres",
password: "postgres",
database: "e_meter_test_#{System.get_env("MIX_TEST_PARTITION")}",
hostname: "localhost",
pool: Ecto.Adapters.SQL.Sandbox
The same goes for the dev.exs config - we need to add the Repo configuration as it was in the test.exs.
config :postgres, Postgres.Repo,
username: "postgres",
password: "postgres",
database: "e_meter_dev",
hostname: "localhost",
show_sensitive_data_on_connection_error: true,
pool_size: 10
Elixir/Phoenix uptime monitor: A word of conclusion
After setting up the project we are ready to go further into this series of articles. In the next one, we will discuss the possibilities and implementation of authentication and authorization. We hope you liked the article! If you have any questions or you want to discuss something let us know in the comments. We appreciate any kind of feedback!