https://iamelevich.dev Ilya Amelevich's Blog 2024-02-17T13:13:08.815Z https://github.com/jpmonette/feed Ilya Amelevich ilya.amelevich@gmail.com https://iamelevich.dev My name is Ilya and I'm software engineer. I'm passionate about web development and open source. I'm also a big fan of Node.js, TypeScript, and Golang. https://iamelevich.dev/og?square=true https://iamelevich.dev/favicon.ico Copyright © Ilya Amelevich 2023 · All Rights Reserved. <![CDATA[asdf-vm - Manage multiple runtime versions with a single CLI tool]]> asdf-version-management-tool 2023-07-23T15:00:00.000Z I'm using asdf-vm for managing multiple runtime versions with a single CLI tool since 2022.

This is a great tool for managing multiple versions of different runtimes like Node.js, Python, Ruby, Java, Elixir, Erlang, etc. It let you define a .tool-versions file in your project root directory and it will automatically switch to that version when you enter the directory. It also allows you to install multiple versions of the same runtime and switch between them on the fly.

Installation

This tool is available for Linux, macOS, and Windows. You can find the installation instructions here. It supports also a lot of shells like bash, fish, zsh, and powershell.

Usage

It's pretty simple to use. You should install plugin that add support of your favourite tool. For example for node.js it looks like this:

asdf plugin add nodejs

Then you can install any version of node.js you want:

asdf install nodejs 16.0.0

And then you can set this version as global:

asdf global nodejs 16.0.0

Or you can set it as local for your project:

asdf local nodejs 16.0.0

More plugin can be found in asdf-community.

.tool-versions

You can also define .tool-versions file in your project root directory and it will automatically switch to that version when you enter the directory. It's pretty useful when you have multiple projects with different versions of the same runtime.

nodejs 16.0.0
python 3.9.5

Or if you don't have such tool installed it will ask you to install it.

Some useful plugins I'm using

  • nodejs - Manage multiple Node.js versions using node-build
  • direnv - Super useful plugin that solves performance issues in asdf and let you manage your environment variables in .envrc file.
  • golang - Manage multiple Go versions, also support detecting go version from go.mod file.
  • golangci-lint - Manage multiple versions of golangci-lint - Go linters runner.
  • pre-commit - Manage multiple versions of pre-commit - A framework for managing and maintaining multi-language pre-commit hooks.
  • k6 - Manage multiple versions of k6 - A modern load testing tool, using Go and JavaScript.
  • python - Manage multiple Python versions using python-build.

Conclusion

I'm using this tool for a long time and I'm pretty happy with it. It's a great tool for managing multiple versions of different runtimes. It's also pretty easy to use and it's well documented. I recommend you to try it out. You can find more information on the official website.

]]>
Ilya Amelevich ilya.amelevich@gmail.com https://iamelevich.dev
<![CDATA[Next.js with Prometheus]]> nextjs-with-prometheus-metrics 2023-07-23T22:00:00.000Z Next.js is a popular React framework that allows for server-side rendering and static site generation. It is used to build production-grade web applications with ease. Prometheus, on the other hand, is an open-source monitoring system that collects metrics from various sources and stores them in a time-series database.

Using Prometheus with Next.js can provide valuable insights into the performance and health of your web application. Here are some benefits of integrating Prometheus with Next.js:

  1. Real-time monitoring: Prometheus provides real-time metrics that can help you identify issues with your Next.js application. For example, you can monitor the response time of your server and detect when it's taking longer than usual to respond to requests.
  2. Customizable dashboards: Prometheus allows you to create custom dashboards that display the metrics you care about the most. You can create graphs and charts that show the performance of your Next.js application over time.
  3. Alerting: Prometheus can send alerts when certain metrics exceed a defined threshold. This can help you quickly identify and address issues before they become major problems.

To integrate Prometheus with Next.js, you can use the prom-client library. This library provides an easy-to-use API that allows you to instrument your code and collect metrics.

But unfortunately, there is no way to track all HTTP requests without using the Custom Server feature of Next.js. In this article I will use fastify with fastify-metrics library for that.

Tools

There list of tools that we will use in this article:

Setup

First, we need to create a new Next.js project. We can do this by running the following command:

npx create-next-app@latest

I will use App Router and TypeScript for this project. So, I will select them in the setup wizard.

✔ What is your project named? … next-prometheus-example
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … No
✔ Would you like to customize the default import alias? … No

After the setup is complete, we can start the development server by running the following command:

npm run dev

Now, we can open the application in our browser and see the default Next.js page.

Next default page

Setup custom server with Fastify

Next.js allows us to create a custom server using the server.js file. We can use this file to configure our server and add custom routes. In this article, we will use Fastify as our server framework. Unfortunatelly Next.js doesn't support Typescript for custom server, but with some hacks we can make it work. If you want to use plain JavaScript, you can skip this step.

Setup TypeScript for custom server

First, let's install the required dependencies:

npm install --save-dev nodemon ts-node

Next let's create server folder with server.ts file inside it. We do it to separate our server code from the rest of the application, but it's not required.

mkdir server
touch server/server.ts

Next, we need to create a nodemon.json file in the root directory of our project. This file will be used by nodemon to run our server.

{
  "watch": ["server/**/*.ts"],
  "exec": "ts-node --project tsconfig.server.json ./server/server.ts",
  "ext": "js ts"
}

Next, we need to create a tsconfig.server.json file in the root directory of our project. This file will be used by ts-node to compile our server code.

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs",
    "outDir": "dist",
    "lib": ["es2019"],
    "target": "es2019",
    "isolatedModules": false,
    "noEmit": false
  },
  "include": ["server/**/*.ts"]
}

As last step there is a need to update package.json file to add new scripts:

{
  "scripts": {
    "dev": "TS_NODE_PROJECT=tsconfig.server.json nodemon --exec node --inspect -r ts-node/register ./server/server.ts",
    "build": "npm run build:server && npm run build:next",
    "build:next": "next build",
    "build:server": "tsc --project tsconfig.server.json",
    "start": "node dist/server/server.js",
    "lint": "next lint"
  }
}

At the end of this step our project will not be able to start, because we don't have server.ts file yet. Let's create it.

Commit with this step

Setup Fastify

First, we need to install the required dependencies:

npm install --save fastify

Next, we need to create a server.ts file in the server folder. This file will be used to configure our server and add custom routes.

import fastify from 'fastify';
import next from 'next';
import { parse } from 'url';

const port = parseInt(process.env.PORT || '3000', 10);
const isDev = process.env.NODE_ENV !== 'production';
const app = next({ dev: isDev, hostname: 'localhost', port });
const handle = app.getRequestHandler();
const server = fastify({
  logger: {
    level: isDev ? 'debug' : 'info'
  }
});

app.prepare().then(async () => {
  server.all('*', async (request, response) => {
    return handle(request.raw, response.raw, parse(request.url, true));
  });
  server
    .listen({
      port,
      host: '0.0.0.0'
    })
    .then(() => {
      console.log('server started');
    });
});

Now, we can start the development server by running the following command:

npm run dev

After the server is started, we can open http://localhost:3000 in our browser and see the default Next.js page.

Commit with this step

Setup Fastify Metrics

First, we need to install the required dependencies:

npm install --save fastify-metrics

Next, we need to update server.ts file in the server folder. This file will be used to configure our server and add custom routes.

import fastify, { FastifyRequest } from 'fastify';
import next from 'next';
import { parse } from 'url';
import metricsPlugin from 'fastify-metrics';

const port = parseInt(process.env.PORT || '3000', 10);
const isDev = process.env.NODE_ENV !== 'production';
const app = next({ dev: isDev, hostname: 'localhost', port });
const handle = app.getRequestHandler();
const server = fastify({
  logger: {
    level: isDev ? 'debug' : 'info'
  }
});

app.prepare().then(async () => {
  // Setup prometheus metrics plugin
  await server.register(metricsPlugin, {
    endpoint: '/api/metrics',
    defaultMetrics: {
      enabled: true,
      labels: {
        name: 'next-prometheus-example',
        version: '0.1.0'
      }
    },
    routeMetrics: {
      groupStatusCodes: true,
      routeBlacklist: ['/api/metrics'],
      customLabels: {
        name: 'next-prometheus-example',
        version: '0.1.0'
      },
      overrides: {
        labels: {
          // This is a custom label for the route name. It will try to use pathname or urls if not provided.
          getRouteLabel: (request: FastifyRequest) => {
            if (request.routeConfig.statsId) {
              return request.routeConfig.statsId;
            }
            const parsedUrl = parse(request.url, true);
            return parsedUrl.pathname ?? request.url;
          }
        }
      }
    }
  });

  server.all('*', async (request, response) => {
    return handle(request.raw, response.raw, parse(request.url, true));
  });
  server
    .listen({
      port,
      host: '0.0.0.0'
    })
    .then(() => {
      console.log('server started');
    });
});

Now, we can start the development server and check the metrics endpoint /api/metrics. Result should be similar to this:

Fastify metrics

Commit with this step

Some customizations that can be useful

  • Use pino-pretty to format logs in development mode
  • You can customize what will be in route label by using statsId property in request. For example, it can be useful to track dynamic routes.
  • You can use routeBlacklist to exclude some routes from metrics. For example, you can exclude /api/metrics endpoint.
  • You can use groupStatusCodes to group status codes. For example, you can group 200 and 201 status codes to 2xx code.
  • You can use customLabels to add custom labels to all metrics.

Conclusion

In this article, we have learned how to integrate Prometheus with Next.js. We have also seen how to use Prometheus to monitor the performance of our Next.js application. If you want to learn more about Prometheus, check out the official documentation at https://prometheus.io/docs/introduction/overview/.

This is not the best way how to monitor Next.js application, but in some cases it can be useful. Also check the official documentation for Monitoring with OpenTelemetry: https://nextjs.org/docs/app/building-your-application/optimizing/open-telemetry

Code

You can find the code for this article here

]]>
Ilya Amelevich ilya.amelevich@gmail.com https://iamelevich.dev
<![CDATA[Automate release process with Release Please]]> automate-release-process-with-release-please 2023-07-24T01:00:00.000Z In this article I will try to explain how to automate the release process of your projects with Release Please and some tools that I'm using with it.

What is Release Please?

From GitHub:

Release Please automates CHANGELOG generation, the creation of GitHub releases, and version bumps for your projects.

It does so by parsing your git history, looking for Conventional Commit messages, and creating release PRs.

It does not handle publication to package managers or handle complex branch management.

In other words it will create a PR with the changes that you made in your project, and it will bump the version of your project based on the changes that you made.

How to use it?

First of all you need to start using Conventional Commit messages, this is a standard for commit messages. It let Release Please know what to add to CHANGELOG and how to bump the version of your project.

I'm using commitlint to enforce this standard in my projects. You can use it with husky or pre-commit to run it before every commit. And also it's good to use it in your CI.

Setup with husky

Install husky. There I will show how to do it with npm, for other package managers you can check the documentation.

npx husky-init && npm install

It will:

  1. Add prepare script to package.json
  2. Create a sample pre-commit hook that you can edit (by default, npm test will run when you commit)
  3. Configure Git hooks path

Install commitlint and the conventional config.

npm install --save-dev @commitlint/{cli,config-conventional}

# Configure commitlint to use conventional config
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

Add a commit-msg hook to run commitlint on every commit.

npx husky add .husky/commit-msg  'npx --no -- commitlint --edit ${1}'

Setup with pre-commit

If you are using pre-commit you can add this to your .pre-commit-config.yaml file.

repos:
  - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
    rev: v9.4.0
    hooks:
      - id: commitlint
        stages: [commit-msg]
        additional_dependencies:
          - '@commitlint/config-conventional'

Setup with CI

For GitHub Actions you can use this action.

Example of usage. File .github/workflows/commitlint.yml.

name: Lint Commit Messages
on: [push, pull_request]

jobs:
  commitlint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: wagoid/commitlint-github-action@v5

Also you can check documentation for more examples.

Setup Release Please

I propose to use release-please-action to automate the release process. It's a GitHub Action that will run Release Please on every push to the main branch.

Example file .github/workflows/release-please.yml:

on:
  push:
    branches:
      - main

permissions:
  contents: write
  pull-requests: write

name: release-please

jobs:
  release-please:
    runs-on: ubuntu-latest
    steps:
      - uses: google-github-actions/release-please-action@v3
        with:
          release-type: node
          package-name: release-please-action

Do not forget to update the package-name with the name of your project and the release-type with the type of your project. You can check the documentation to find supported release types.

Workflow example

  1. You create a separate branch for your changes.
  2. You make your changes and commit them with a conventional commit message. You should use fix or feat types for your commits to make release happen.
  3. You push your changes to the remote repository.
  4. You create a PR to merge your changes to the main branch.
  5. After merging your changes to the main branch, the release-please-action will run and create a PR with the changes that you made and bump the version of your project.
  6. When you are ready to release your changes you can merge the PR created by release-please-action.
  7. After merging the PR, release-please-action will create a GitHub release with the changes that you made and the new version of your project.

Conclusion

Release Please is a great tool to automate the release process of your projects. It's easy to setup and use. I hope this article will help you to start using it in your projects.

]]>
Ilya Amelevich ilya.amelevich@gmail.com https://iamelevich.dev