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.
Table of Contents
- Introduction to V
- V lang REST API
- Create Docker file for v lang
- CI/CD: Setup GitHub Action for Publishing Image to ghcr.io
- Create Azure App Service to deploy V lang REST API (Publish mode- Docker Container)
- Troubleshoot App Service Deployment
- Perform CRUD
- Full Source Code
- Video
- Resources
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.
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 imagethevlang/vlang:latest
ENV PORT 8080
tells that we are exposing an environment variable namePORT
with the value set to8080
EXPOSE:8080
tells the container to expose the port8080
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 commandv 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 thePublish 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 asDocker 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
asLinux
. - Choose the
Region
which is nearest to the consumers of your web application. For now, we will chooseEast 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 choosingBasic 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 aDockerfile
. In case you have multiple containers to be deployed, you can choose Docker Compose. - Choose the
Image Source
asPrivate Registry
.
- Under the
Private Registry Options
, provide the input as mentioned below. - Set the
Server URL
ashttps://ghcr.io
- Provide the
Username
which is the username you use to login to your GitHub account. - Provide the
Image and tag
in the formatgithubusername/repositoryname:tag
. In my case itswindson/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
toWEBSITES_PORT
- Set
Value
to"8080"
. It was provided in between double quotes. - Click on
OK
- Click on
Save
and then click onContinue
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.
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
- Installing V Lang on Ubuntu
- How to Install V Lang on Windows OS
- How to Debug V Programming Language in VS Code on Windows 10 OS
- Check out my book on V, Getting Started with V Programming
- V lang official website: https://vlang.io
- Join V lang discord: https://discord.gg/vlang