How to deploy V lang REST API on Azure

In this tutorial we will see how to deploy a V lang based REST API on Azure App Service in the containerized form. Before, we proceed let’s look at the very short and sweet introduction to V.

How to deploy V lang REST API on Azure - TutLinks
How to deploy V lang REST API on Azure – TutLinks

Table of Contents

Introduction to V

V lang is a statically typed compiled programming language. V is written in V (not using C 😅 unlike other programming languages). V is currently in beta phase. V has a very wide range of features and it’s not just a programming language. Rather than treating V as programming language, it is acceptable to treat it as a full blown eco-system. The following are the features of V eco-system.

  • An Operating system named Vinix, written in V.
  • A simple but, powerful programming language, V, itself!
  • Powerful concurrency capabilities using V routines, channels, waitgroups, mutex
  • Inbuilt web framework vWeb
  • Inbuilt Object Relational Mapper, named as orm (a.k.a) v-orm
  • C2V conversion, ables you to convert your c or c++ code to V
  • Go2V conversion, ability to convert your Go lang code to V lang
  • Cross platform compilation and builds such as one code to support all the operating systems and android
  • Cross language compilation such as V to JavaScript and WASM
  • Cross platform shell scripting. Not using kshell or z shell, but using v shell (Dev-Ops folks you heard it right!!)
  • Native Cross platform GUI library ui
  • And many more and hence it’s an eco-system

In this tutorial, we will focus on dockerizing a V lang web application that is implemented using vWeb that talks to sqlite database using built-in orm.

V lang REST API

Following is the high level overview of our V lang REST API

Programming Langauge & web framework

  • The REST API is implemented using V programming language with it’s in-built module vweb.
  • The responses were in the JSON format. For this JSON format, and json module of V is used to acheive this.

Database

It talks to a sqlite database with the help of V’s inbuilt module sqlite.

Packaging followed for Deployment

  • The module implemented will be packaged using a Dockerfile and a docker image is created using GitHub Actions
  • We will be using the official V lang’s base image thevlang/vlang:latest to build the application code.

Overview of V lang RESTful web application

As the focus is on deploying the V lang web app on Azure App Service, we will be using the code that already exists here. This code has a RESTful implementation of endpoints of an entity Note that allows you to perform CRUD operations using vweb.

The repository has the following directory structures with files as shown below.

E:\vlang-app
│   .gitignore
│   Dockerfile
│   README.md
│
├───.github
│   └───workflows
│           docker-publish.yml
│
└───app
        app.v
        note.v
        util.v
        v.mod

Let’s look at the contents of app directory which has a application code of our vweb RESTful code.

Getting Started with V Programming - TutLinks

Learn more about the basics of V in my book Getting Started with V Programming

Getting Started with V Programming: An end-to-end guide to adopting the V language from basic variables and modules to advanced concurrency

First lets start with the code in util.v

// util.v
module main

import json

struct CustomResponse {
	status  int
	message string
}

fn (c CustomResponse) to_json() string {
	return json.encode(c)
}

const (
	invalid_json   = 'Invalid JSON Payload'
	note_not_found = 'Note not found'
	unique_message = 'Please provide a unique message for Note'
)

The util.v has the common functoinality that can be used across the application. The common functionality may include constants, custom responses etc,.

Now the following is the code in note.v

// note.v
module main

import json
import vweb

[table: 'Notes']
struct Note {
	id      int    [primary; sql: serial]
	message string [sql: 'detail'; unique]
	status  bool   [nonull]
}

fn (n Note) to_json() string {
	return json.encode(n)
}

['/notes'; post]
fn (mut app App) create() vweb.Result {
	// malformed json
	n := json.decode(Note, app.req.data) or {
		app.set_status(400, 'Bad Request')
		er := CustomResponse{400, invalid_json}
		return app.json(er.to_json())
	}

	// before we save, we must ensure the note's message is unique
	notes_found := sql app.db {
		select from Note where message == n.message
	}
	if notes_found.len > 0 {
		app.set_status(400, 'Bad Request')
		er := CustomResponse{400, unique_message}
		return app.json(er.to_json())
	}

	// save to db
	sql app.db {
		insert n into Note
	}

	// retrieve the last id from the db to build full Note object
	new_id := app.db.last_id() as int

	// build new note object including the new_id and send it as JSON response
	note_created := Note{new_id, n.message, n.status}
	app.set_status(201, 'created')
	app.add_header('Content-Location', '/notes/$new_id')
	return app.json(note_created.to_json())
}

['/notes/:id'; get]
fn (mut app App) read(id int) vweb.Result {
	n := sql app.db {
		select from Note where id == id
	}

	// check if note exists
	if n.id != id {
		app.set_status(404, 'Not Found')
		er := CustomResponse{400, note_not_found}
		return app.json(er.to_json())
	}

	// found note, return it
	ret := json.encode(n)
	app.set_status(200, 'OK')
	return app.json(ret)
}

['/notes/'; get]
fn (mut app App) read_all() vweb.Result {
	n := sql app.db {
		select from Note
	}

	ret := json.encode(n)
	app.set_status(200, 'OK')
	return app.json(ret)
}

['/notes/:id'; put]
fn (mut app App) update(id int) vweb.Result {
	// malformed json
	n := json.decode(Note, app.req.data) or {
		app.set_status(400, 'Bad Request')
		er := CustomResponse{400, invalid_json}
		return app.json(er.to_json())
	}

	// check if note to be updated exists
	note_to_update := sql app.db {
		select from Note where id == id
	}

	if note_to_update.id != id {
		app.set_status(404, 'Not Found')
		er := CustomResponse{404, note_not_found}
		return app.json(er.to_json())
	}

	// before update, we must ensure the note's message is unique
	// id != id for idempotency
	// message == n.message for unique check
	res := sql app.db {
		select from Note where message == n.message && id != id
	}

	if res.len > 0 {
		app.set_status(400, 'Bad Request')
		er := CustomResponse{400, unique_message}
		return app.json(er.to_json())
	}

	// update the note
	sql app.db {
		update Note set message = n.message, status = n.status where id == id
	}

	// build the updated note using the :id and request body
	// instead of making one more db call
	updated_note := Note{id, n.message, n.status}

	ret := json.encode(updated_note)
	app.set_status(200, 'OK')
	return app.json(ret)
}

['/notes/:id'; delete]
fn (mut app App) delete(id int) vweb.Result {
	sql app.db {
		delete from Note where id == id
	}
	app.set_status(204, 'No Content')
	return app.ok('')
}

The note.v as we notice has a struct Note that represents the table in the form of a struct and a struct method to_json to represent a Note in JSON format. It also includes CRUD RESTful endpoints which actually interact with the table and also perform reading the requests and building the response in JSON.

Now let’s look at the app.v file.

// app.v
module main

import vweb
import sqlite

struct App {
	vweb.Context
mut:
	db sqlite.DB
}

fn main() {
	db := sqlite.connect('notes.db') or { panic(err) }
	db.exec('drop table if exists Notes')
	sql db {
		create table Note
	}
	http_port := 8080
	app := &App{
		db: db
	}
	vweb.run(app, http_port)
}

['/'; get]
fn (mut app App) hello() vweb.Result {
	return app.text('Welcome to TutLinks!')
}

The app.v is the entry point for the vweb RESTful microservice of Notes. It has App struct that holds web context of vweb as vweb.Context and also the instance of DB connectoin.

The main function connects to the sqlite database. For the demonstration purposes, the main function actually drops the Note table and re-creates it everytime you restart the application. So be sure if you want to deploy it for production purpose to remove this logic of drop and recreation of table. The main function then finally runs vweb by providing the app instance that holds db and its vweb.Context and the http_port as 8080.

This file also has a default / endpoint that greets us with the message 'Welcome to TutLinks!'

The file v.mod has the default module related information which you can changes the values according to your needs.

Module {
	name: 'app'
	description: 'A simple REST API implemented using vlang and vweb'
	version: '0.0.0'
	license: 'MIT'
	dependencies: []
}

Now we will look at the Dockerfile and see how to containerize the V web application.

Create Docker file for v lang

We will Dockerize our V lang web application. For that in the root directory of our project add a new file named Dockerfile. Copy the following into your Dockerfile.

FROM thevlang/vlang:latest

ENV PORT 8080
EXPOSE 8080

WORKDIR /code

COPY ./app /code/app

ENTRYPOINT ["v", "run", "app"]

The file as various instructions. Let’s look at the significant ones.

  • FROM thevlang/vlang:latest tells the docker engine to build our target image on the base image thevlang/vlang:latest
  • ENV PORT 8080 tells that we are exposing an environment variable name PORT with the value set to 8080
  • EXPOSE:8080 tells the container to expose the port 8080 which our applications is configured to run on.
  • WORKDIR /code indicates that on docker container, /code will be the working directory. This also means that any commands run will be relative to the /code directory as the present working directory for the commands.
  • COPY ./app /code/app tells to copy all the files of app module to the image’s directory path located at /copy/app
  • ENTRYPOINT ["v", "run", "app"] tells the docker engine that the entry point or in order to run this application, we need to run the command v run app represented in the format that Docker engine understand.

Now, we will push this code with with aforementioned directory structure to the GitHub repository. Now let’s proceed and look at creating the GitHub Action.

CI/CD: Setup GitHub Action for Publishing Image to ghcr.io

We will now create a GitHub Action that builds the image of the v web application, and pushes it to the GitHub container registry, ghcr.io. This GitHub Action is configured in such a way that, whenever there is a commit or push happens to main branch, this GitHub Action is executed.

To create GitHub Action that build and publishes the image to ghcr.io, perform the following steps.

  • Navigate to Actions tab of your GitHub repository.
  • Click on New Workflow and choose the Publish Docker Container workflow.
  • Make changes according to your need, but it is recommended to leave it as is and commit and push the changes.

You will now see a new file with .yml extension in the .github/workflows/ directory created in the root of your code. The file is named docker-publish.yml and its contents will be as follows.

name: Docker

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

on:
  push:
    branches: [ "main" ]
    # Publish semver tags as releases.
    tags: [ 'v*.*.*' ]
  pull_request:
    branches: [ "main" ]

env:
  # Use docker.io for Docker Hub if empty
  REGISTRY: ghcr.io
  # github.repository as <account>/<repo>
  IMAGE_NAME: ${{ github.repository }}


jobs:
  build:

    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      # This is used to complete the identity challenge
      # with sigstore/fulcio when running outside of PRs.
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      # Install the cosign tool except on PR
      # https://github.com/sigstore/cosign-installer
      - name: Install cosign
        if: github.event_name != 'pull_request'
        uses: sigstore/cosign-installer@7e0881f8fe90b25e305bbf0309761e9314607e25
        with:
          cosign-release: 'v1.9.0'


      # Workaround: https://github.com/docker/build-push-action/issues/461
      - name: Setup Docker buildx
        uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf

      # Login against a Docker registry except on PR
      # https://github.com/docker/login-action
      - name: Log into registry ${{ env.REGISTRY }}
        if: github.event_name != 'pull_request'
        uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Extract metadata (tags, labels) for Docker
      # https://github.com/docker/metadata-action
      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      # Build and push Docker image with Buildx (don't push on PR)
      # https://github.com/docker/build-push-action
      - name: Build and push Docker image
        id: build-and-push
        uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

      # Sign the resulting Docker image digest except on PRs.
      # This will only write to the public Rekor transparency log when the Docker
      # repository is public to avoid leaking data.  If you would like to publish
      # transparency data even for private images, pass --force to cosign below.
      # https://github.com/sigstore/cosign
      - name: Sign the published Docker image
        if: ${{ github.event_name != 'pull_request' }}
        env:
          COSIGN_EXPERIMENTAL: "true"
        # This step uses the identity token to provision an ephemeral certificate
        # against the sigstore community Fulcio instance.
        run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign {}@${{ steps.build-and-push.outputs.digest }}

Create Azure App Service to deploy V lang REST API (Publish mode- Docker Container)

Navigate to Azure Portal https://portal.azure.com

Create an App Service after navigating to App Services section. You will land on Create Web App page. We will follow the steps to create our App Service as detailed below.

In the Create Web App page under the Basics page, set up the following details.

Basics Tab

Under the Project Details,

  • Choose the Subscription and Resource Group.
  • If resource group is not present, click on new resource group and provide it a name.

Under the Instance Details,

  • Provide the Name for the Azure app service
  • Choose the Publish mode as Docker Container. This is mandatory as we have Dockerfile and have dockerized our application and the application image is pushed on to ghcr.io.
  • Choose the Operating System as Linux.
  • Choose the Region which is nearest to the consumers of your web application. For now, we will choose East US.

Under the App Service Plan

  • We will optionally name the Linux app service plan.
  • The SKU and Size can be chosen according to your need. For the demonstration purposes, I’ll be choosing Basic B1.

Docker Tab

In the Create Web App page under the Docker tab, set up the following details.

  • Choose the Single Container as we have a Dockerfile. In case you have multiple containers to be deployed, you can choose Docker Compose.
  • Choose the Image Source as Private Registry.
  • Under the Private Registry Options, provide the input as mentioned below.
  • Set the Server URL as https://ghcr.io
  • Provide the Username which is the username you use to login to your GitHub account.
  • Provide the Image and tag in the format githubusername/repositoryname:tag. In my case its windson/vlang-api:main.

Optionally, set if you have any Startup Command. Or leave it blank and proceed next.

Leave the defaults in Networking, Monitoring & Tags section tabs and click on Review + Create.

Now your Azure App Service will be created in 3-5 minutes.

Troubleshoot App Service Deployment

Once the Azure App Service is deployed, you can navigate to the url <your-app-service name>.azurewebsites.net.

If it works all good, otherwise the following steps will help you troubleshoot your deployment.

Access Container logs on Azure App Service

Even before we troubleshoot, we need to know what’s wrong with the deployment if at all there is any. So, navigate to deployed Azure App Service and under the section Deployment click on Deployment Center. You will see three tabs Settings, Logs and FTP Credentials.

The Settings tab will have options to connect to the container registry such as ghcr.io or docker hub.

Ensure you have provided your credentials properly to connect to the GitHub account.

The Logs tab is the place where you can see the container logs. Make use of the Refresh button available to tail for the recent logs if any. You will also have the option to download the logs.

Observing the logs generated by the container will help you troubleshoot the application or deployment issues.

Typical issues I have faced were, Image Pull failed. Defaulting to local copy if present. This container log clearly indicates that the Azure App Service is not able to establish handshake to ghcr.io and pull the image. So navigate back to Settings tab and ensure you’ve provided proper credentials.

Sometimes it is most common that, even when you provide proper credentials during App Service creation, the deployment will try to pull the image from docker hub. This can be observed on logs in the form of url registry-1.docker.io/v2/ on container logs.

This seems to be faulty behavior by the App Service. So, ensure you re-apply the Settings and save them to take effect.

App Service Configuration Port Setting

Again looking at the container logs, I happen to see the container log that said Container didn't respond to HTTP pings on port : 8080, failing site to start.

In order to fix this, navigate to App service and under the Settings side menu, click on Configuration.

In the Application Settings tab, click on + New application setting and provide the following

  • Set Name to WEBSITES_PORT
  • Set Value to "8080". It was provided in between double quotes.
  • Click on OK
  • Click on Save and then click on Continue to confirm your changes.

Now, navigate to Overview and Restart. Wait for the restart to take effect. After restart, allow the container to warm up, which typically takes ~240 seconds and then browse the application.

You will landed on the home page of your dockerized deployment of V Web application on Azure App Service.

Perform CRUD

Now let’s perform some CRUD operations such as Create and Read Notes, using POST and GET HTTP Verbs respectively. If your app service url is https://vlang-api.azurewebsites.net then the POST and GET Command will be as follows.

POST Notes

To add a Note, we will send POST request to the https://vlang-api.azurewebsites.net url with the payload as shown below in the curl command.

curl --location --request POST 'https://vlang-api.azurewebsites.net/notes/' \
--header 'Content-Type: application/json' \
--data-raw '{
    "message" : "Check the status of the college application",
    "status" : false
}'

The response will appear something like below

{ "id" : 1, "message" : "Check the status of the college application", "status" : false }
curl --location --request POST 'https://vlang-api.azurewebsites.net/notes/' \
--header 'Content-Type: application/json' \
--data-raw '{
    "message" : "Go to School!",
    "status" : false
}'

The response will appear something like blow this time.

{ "id" : 2, "message" : "Go to School!", "status" : false }

Get Notes

Now, lets look for all the notes that we have by sending the curl GET command from bash shell as follows.

curl --location --request GET 'https://vlang-api.azurewebsites.net/notes/'

And you will see the following response which shows the collection of notes as follows.

[{ "id" : 1, "message" : "Check the status of the college application", "status" : false },{ "id" : 2, "message" : "Go to School!", "status" : false }]

Full Source Code

The full source code used in this deployment of V programming on Azure is available at https://github.com/windson/vlang-api.

Video

The following video will show you the steps to deploy v web application on Azure App Service. Once deployed, it will also show you how to perform CRUD operations on vweb REST API on Azure App Service deployment.

How to deploy V lang REST API on Azure – TutLinks

If you found this tutorial helpful, please do (Ctrl + D) to 🔖 bookmark this page. If you are interested in learning V programming, check out my book Getting Started with V Programming.

Resources

Navule Pavan Kumar Rao

I am a Full Stack Software Engineer with the Product Development experience in Banking, Finance, Corporate Tax and Automobile domains. I use SOLID Programming Principles and Design Patterns and Architect Software Solutions that scale using C#, .NET, Python, PHP and TDD. I am an expert in deployment of the Software Applications to Cloud Platforms such as Azure, GCP and non cloud On-Premise Infrastructures using shell scripts that become a part of CI/CD. I pursued Executive M.Tech in Data Science from IIT, Hyderabad (Indian Institute of Technology, Hyderabad) and hold B.Tech in Electonics and Communications Engineering from Vaagdevi Institute of Technology & Science.

Leave a Reply