Home Web Development Web Development with Julia and Genie

Web Development with Julia and Genie

By Ivo Balbaert , Adrian Salceanu
books-svg-icon Book
eBook $33.99 $22.99
Print $41.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $33.99 $22.99
Print $41.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Chapter 1: Julia Programming Overview
About this book
Julia’s high-performance and scalability characteristics and its extensive number of packages for visualizing data make it an excellent fit for developing web apps, web services, and web dashboards. The two parts of this book provide complete coverage to build your skills in web development. First, you'll refresh your knowledge of the main concepts in Julia that will further be used in web development. Then, you’ll use Julia’s standard web packages and examine how the building blocks of the web such as TCP-IP, web sockets, HTTP protocol, and so on are implemented in Julia’s standard library. Each topic is discussed and developed into code that you can apply in new projects, from static websites to dashboards. You’ll also understand how to choose the right Julia framework for a project. The second part of the book talks about the Genie framework. You’ll learn how to build a traditional to do app following the MVC design pattern. Next, you’ll add a REST API to this project, including testing and documentation. Later, you’ll explore the various ways of deploying an app in production, including authentication functionality. Finally, you’ll work on an interactive data dashboard, making various chart types and filters. By the end of this book, you’ll be able to build interactive web solutions on a large scale with a Julia-based web framework.
Publication date:
November 2022
Publisher
Packt
Pages
254
ISBN
9781801811132

 

Julia Programming Overview

Julia is a high-performance, open source computing language, mostly applied to data analysis, machine learning, and other scientific and technical computing applications.

The language combines the ease of use of Python or R with the speed of C and eliminates the need for using two languages to develop data intensive applications. It is as readable and high-level as Python and because of its type inference and optional typing, behaves as a dynamic language. It is also as fast as C, but much more readable. As a new programming language, Julia borrowed some of the best features from other modern languages. For example, like Ruby, it doesn’t use semicolons or curly braces for delimiting code; instead, it uses a more Pascal-like syntax, with end to indicate where a code structure stops.

Julia is not a classic object-oriented language like Java; instead, it is more function-oriented, but it also has a struct data type like C. Functions that act on and transform data are the basic building blocks. The language also has built-in parallel computing capabilities and can scale up very easily.

Julia also provides an extensive standard library from the start. The language’s usage and popularity are steadily rising; it has been downloaded by users from more than 10,000 companies and is used at over 1,500 universities worldwide (https://juliacomputing.com/media/2022/02/julia-turns-ten-years-old/).

This chapter will touch on the main Julia concepts we will need in web development including types, flow control, functions, packages, and modules. We will introduce some examples relating to the ToDo app project theme for Part 2 of this book.

We’ll also show code snippets from the Genie framework that are used in Part 2. We wrap up with a section on how Julia works internally, which makes us better understand Julia’s efficacy in web development. By the end of this chapter, your Julia knowledge will be refreshed, and you’ll be much better prepared to grasp the rest of the book.

In this chapter, we will cover the following topics:

  • Working with Julia
  • Types, flow controls, and functions in Julia
  • Useful techniques in Julia web development
  • Using Julia modules and packages
  • How Julia works
  • Why Julia is a good fit for web development
 

Technical requirements

To follow through with all the exercises in this chapter and the rest of the book, you will need the following:

All the code examples in this book have been run on a Windows machine. You may find subtle differences in output if you are using Linux/macOS. Where necessary, command variations for both machines have been specified.

The complete source code for this chapter can be found at https://github.com/PacktPublishing/Web-Development-with-Julia-and-Genie/tree/main/Chapter1.

 

Working with Julia

In this section, we will set up a standard Julia development environment, and learn how to work with Julia scripts as well as the Read–Eval–Print Loop (REPL). The REPL allows you to work with Julia in an interactive way, trying out expressions, function calls, and even executing whole programs.

You’re good to go when typing in julia at the terminal starts up the REPL:

Figure 1.1 – The Julia REPL

Figure 1.1 – The Julia REPL

Using the REPL to use Julia interactively

Try typing in 256^2, giving the result 65536, or rand(), which gives you a random number between 0 and 1, for example, 0.02925477322848513. We’ll use the REPL extensively throughout the book, and the Genie web framework discussed in Part 2 allows you to build your entire web app from the REPL.

Some useful REPL keyboard shortcuts you will use often include the following:

  • Ctrl + D: To exit the REPL
  • Ctrl + L: To clear the screen
  • Ctrl + C: To get a new prompt
  • Up and down arrows: To reuse recent commands

To see which folder you are in, type pwd(). To change the current folder, execute cd("path/to/folder").

A feature we’ll use a lot in Genie is creating new files from within the REPL with the touch command. For example, to create an empty testset.jl file in an existing folder structure, testing/phase1, enter the following:

julia> touch(joinpath("testing", "phase1", "testset.jl"))

The REPL returns testing\\phase1\\testset.jl on Windows and testing/phase1/testset.jl on *nix systems.

The joinpath function constructs the directory path starting in the current folder, and touch creates the file.

Using the package mode to jump-start a project

The Julia ecosystem encompasses thousands of libraries, called packages (see https://julialang.org/packages/), for which Julia has a built-in package manager, Pkg.

The REPL has a special mode for working with packages, which is started by typing ] at julia> prompt, which brings you to package mode: (@v1.8) pkg>.

Some useful commands in this mode to type in after the pkg> prompt are as follows:

  • st or status: Gets a list of all the packages installed in your environment.
  • add PackageName: Adds a new package (you can add several packages separated by , if needed).
  • up or update: Updates all your packages.
  • up or update PackageName: Updates a specific package.
  • activate .: Activates the current project environment (see the Packages and projects section under Using Julia modules and packages). rm or remove PackageName: Removes a specific package.
  • ?: Lists all available commands.
  • The backspace key: Exits the pkg> mode.

In the next section, Parsing a CSV file, we will work with a comma-separated values (CSV) file of to-do items. The CSV package can be imported and set up to be used in your Julia REPL by typing the following:

julia> using Pkg
julia> Pkg.add("CSV")
julia> using CSV

The last line of the preceding command brings the definitions of the CSV package into scope.

Alternatively, from the package mode, use the following:

]:
(@v1.8) pkg> add CSV

The preceding command installs all packages CSV depends on and then precompiles them. This way, the project gets a jump-start, because the just-in-time (JIT) compiler doesn’t have to do this work anymore.

Using Julia with the VS Code plugin

Julia code can also be saved and edited in files with a .jl extension. Numerous IDEs exist to do that. In this book, we’ll use the VS Code platform with the excellent Julia plugin, which provides syntax highlighting and completion, lookup definitions, and plotting among many other features.

A handy way to start VS Code from the terminal prompt is by typing in code.

Search in the Extensions tab for Julia and install it. Then, open up a new file, and type in println("Hi Web World from Julia!"), and save it as hiweb.jl.

Run the program in VS Code with F5 to see the string printed out. Or start the REPL and type the following to get the same result:

julia> include("hiweb.jl")

include evaluates the contents of the input source file.

Or, from a terminal prompt, execute a Julia source file simply by typing the following:

julia hiweb.jl

In all cases, the output will be Hi Web World from Julia!

The include command loads in the Julia file and executes the code.

Continuing the example from the previous section, if you want to start editing the newly created testset.jl file in VS Code when you are working in the REPL, simply type the following:

 julia> edit(joinpath("testing", "phase1", "testset.jl"))

Now that you have some idea of working with Julia using the REPL, in package mode, and using VS Code, let us dig deeper into understanding the basics of the language. In the next section, we will explore some of the basic types, flow controls, functions, and methods in Julia.

 

Types, flow controls, and functions in Julia

In this section, we will discuss some basic concepts in Julia and start applying them to our ToDo project. Let us start by understanding the types of data that can be used in Julia.

Types

To achieve its high level of performance, Julia needs to know the types of data it will handle at either compile time or runtime. You can annotate a local function variable x with a type Int16 explicitly, like in x::Int16 = 42.

But you can just as well write x = 42. If you then ask for the variable’s type with typeof(x), you get Int64 (or Int32 on 32-bit operating systems). So, you see, there is a difference: if you know Int16 is sufficient, you can save memory here, which can be important if there are many such cases.

Explicit typing is sometimes done for function arguments and can enhance performance. Types can also be added at a later stage of the project. Also, although Julia allows it, do not change a variable’s type: this is very bad for performance. To test whether a variable is of a certain type, use the isa function: isa(x, Int64) returns true.

Julia has an abundance of built-in types, ranging from Char, Bool, Int8 to Int128 (and its unsigned counterparts, UInt8 and so on), Float16 to Float64, String, Array, Dict, and Set.

Strings containing variables or expressions can be constructed by string interpolation: when x has the value 108, the string "The value of x is $x" is evaluated to "The value of x is 108". An expression must be placed within parentheses, like "6 * 2 is $(6 * 2)", which evaluates to "6 * 2 is 12".

It is best practice not to use global variables as they cause bugs and have major performance issues. It is better to use constants, such as const var1 = 3, which can’t be modified. In this case, Julia’s JIT compiler can generate much more efficient code.

As an alternative to global variables, you can use Refs as is done in the Genie framework, like this:

const var = Ref{Float64}(0.0)
var[] = 20.0

That way, you make certain that the type of var will not change.

Types follow a hierarchy, with the Any type at the top, which, as the name says, allows any type for such a variable. In Figure 1.2, we show a part of this type tree:

Figure 1.2 – Part of Julia’s type hierarchy [Adapted from Type-hierarchy-for-julia-numbers.png made available at https://commons.wikimedia.org/wiki/File:Type-hierarchy-for-julia-numbers.png by Cormullion, licensed under the CC BY-SA 4.0 license (https://creativecommons.org/licenses/by-sa/4.0/deed.en)]

Figure 1.2 – Part of Julia’s type hierarchy [Adapted from Type-hierarchy-for-julia-numbers.png made available at https://commons.wikimedia.org/wiki/File:Type-hierarchy-for-julia-numbers.png by Cormullion, licensed under the CC BY-SA 4.0 license (https://creativecommons.org/licenses/by-sa/4.0/deed.en)]

In the preceding figure, we see that the Integer type has subtypes Unsigned, Signed, and Bool.

A subtype (a kind of inheritance relationship) is indicated in code as follows:

Bool <: Integer

Types with subtypes are abstract types; we cannot create an instance of this type. The types that have no subtypes (the leaf nodes) are concrete types; only these can have data. For example, Bool variables can have the values true and false. A variable b declared as Integer has in fact the type Int64:

b :: Integer = 42
typeof(b)   # => Int64

To describe a ToDo-item, we need several data items or fields. Let us have a look at what types of values each field can take using some examples:

  • id: Here, we could add an integer of type Int32, such as 1.
  • description: Here, we can only use a String, such as "Getting groceries".
  • completed: This field will take a Bool value, which is initially set to false.
  • created: This field takes the Date type. This type lives in the Dates module, so to make it known to Julia, we have to say so in code: using Dates.
  • priority: This field could take an integer between 1 to 10.

We could group all this data into an array-like type, called a Vector. Because we have all kinds of items of different types, the type of the items is Any. So, our Vector would look as follows:

julia> todo1 = [1, "Getting groceries", false, Date("2022-04-01", "yyyy-mm-dd"), 5]

Running the preceding code would give us the following output:

5-element Vector{Any}:
1
"Getting groceries"
false
2022-04-01
5

To get the description, we have to use an index, todo1[2]; the index is 2 because Julia array indices start from 1.

A better way to group the data is using a struct:

julia> mutable struct ToDo
                id::Int32
                description::String
                completed::Bool
                created::Date
                priority::Int8
       end

Then, we can define the same todo item as in the preceding code as a struct instance:

julia> todo1 = ToDo(1, "Getting groceries", false, Date("2022-04-01", "yyyy-mm-dd"), 5)

Now, instead of using an index, we can directly ask for a particular field, for example, the todo’s description:

julia> todo1.description
"Getting groceries"

Or we can indicate when the item is dealt with:

julia> todo1.completed = true

To nicely print out the data of a struct, use the show (struct) or display (struct) functions.

Another thing that we will see used a lot in Genie is symbols. These are names or expressions prefixed by a colon, for example, :customer. Symbols are immutable and hashed by the language for fast comparison. A symbol is used to represent a variable in metaprogramming.

The : quote operator prevents Julia from evaluating the code of the expression. Instead, that code will be evaluated when the expression is passed to eval at runtime. The following code snippet shows this behavior:

ex = :(a + b * c + 1)
a = 1
b = 2
c = 3
println("ex is $ex")  # => ex is a + b * c + 1
println("ex is $( eval(ex) )")  # => ex is 8

See the Useful techniques in Julia web development section for how symbols can be used.

In this section, we have seen that the use of the appropriate types is very important in Julia: it can make your code more performant and readable.

Flow controls

Julia is equipped with all the standard flow controls, including the following:

  • if elseif else end: Branching on a condition.
  • for in end: Looping with a counter or iterating over a set of values.
  • while end: Looping while testing on a condition.
  • break: Used to jump out of a loop.
  • continue: Used to continue with the loop’s next iteration.
  • throw: Used to throw exceptions and use code that can go wrong in a try construct. Here is an example:
    try
    # dangerous code
    catch ex # handle possible exceptions
    finally  # clean up resources
    end

You can see a concrete usage example of try/catch in the echo server example in the Making a TCP echo server with TCP-IP Sockets section of Chapter 2, Using Julia Standard Web Packages. However, don’t overuse this feature; it can degrade performance (for those curious, this is because the runtime needs to add the stack trace to the exception, and afterward, needs to unwind it).

  • You can also make your own custom exceptions like this:
    mutable struct CustomException <: Exception
    # fields
    end

Let’s see an example of flow control in action. Here is how we compare the priorities of todos:

if todo2.priority > todo1.priority
    println("Better do todo2 first")
else
    println("Better do todo1 first")
end

So, you see, Julia has all the basic flow controls like any standard programming language.

Functions and methods

Functions are the basic tools in Julia. They are defined as follows:

function name(params)
 # body code
end

Alternatively, we can use a one-liner:

name(params) = # body code

Functions are very powerful in Julia. They support optional arguments (which provide default values when no value is provided) and keyword arguments (here the argument’s arg1 value must be specified as func(arg1=value) when the function is called). Functions can be nested inside other functions, passed as a parameter to a function, and returned as a value from a function. Neither argument types nor return types are required, but they can be specified using the :: notation.

Values are not copied when they are passed to functions; instead, the arguments are new variable bindings for these values.

To better indicate that a function changes its argument, append ! to its name, for example:

julia> increase_priority!(todo) = todo.priority += 1
julia> todo1.priority
5
julia> increase_priority!(todo1)
6
julia> todo1.priority
6

In the preceding code, notice that we don’t need to indicate the type of the argument; todo functions are by default generic, meaning that in principle, they can take any type. The JIT compiler will generate a different compiled version of the function each time it is called with arguments of a new type. A concrete version of a function for a specific combination of argument types is called a method in Julia. You can define different methods of a function (also called function overloading) by using a different number of arguments or arguments with different types with the same function name.

For example, here are two overloading methods for a move function:

abstract type Vehicle end
function move(v::Vehicle, dist::Float64)
  println("Moving by $dist meters")
end
function move(v::Vehicle, dist::LightYears)
  println("Blazing across $dist light years")
end

The Julia runtime stores a list of all the methods in a virtual method table (vtable) on the function itself. Methods in Julia belong to a function, and not to a particular type as in object-oriented languages.

In practice, however, an error will be generated when the function cannot be applied for the supplied type. An example of such an error is as follows:

julia> increase_priority!("does this work?")

If you run the preceding code, you will get the following output:

ERROR: type String has no field priority

One could say that a function belongs to multiple types, or that a function is specialized or overloaded for different combinations of types. This key feature of Julia is called multiple dispatch, meaning that the execution can be dispatched on multiple argument types. Julia’s ability to compile code that reads like a high-level dynamic language into machine code that performs like C almost entirely derives from this ability, which neither Python, C++, nor Fortran implement.

A function can also take a variable number of arguments, indicated by three dots (…, called the splat operator). For example, the validate function takes two arguments, a and b, and then a variable number of values (args…):

validate(a, b, args…)

The function can be called as validate(1, 2, 3, 4, 5), or as validate(1, 2, 3), or even validate(1, 2). The type of args… is Vararg; it can be type annotated as args::Vararg{Any}.

If you see what seems to be a function call prefixed with an @ (such as @error or @authenticated! in Genie), you are looking at a macro call. A macro is code that is modified and expanded at parse-time, so before the code is actually compiled. For example, @show is a macro that displays the expression to be evaluated and its result, and then returns the value of the result. You can see examples in action with @async in the Making a TCP echo server with TCP-IP Sockets section in Chapter 2, Using Julia Standard Web Packages.

In this section, we saw that Julia has pretty much what you expect in any modern programming language: a complete type system, normal flow controls, exception handling, and versatile functions. We cannot review all the methods that Julia has to offer in this book, but detailed information regarding methods can be found in the Julia documentation: https://docs.julialang.org/en/v1/.

Now that you know some basic features that define the Julia language, let us explore some useful techniques that can help us further to take advantage of the speed of Julia.

 

Useful techniques in Julia web development

In this section, we will highlight some methods and techniques that you will see used often in Julia web development and that we will also use in the project in Part 2. Here is a list of these key techniques:

  • Multi-line strings: These strings are delineated with """. These are often useful in web apps to use chunks of HTML code in a variable as follows:
    form = """ <form action="/" method="POST" enctype="multipart/form-data"> <input type="text" name="name" value="" placeholder="What's your name?" /> <input type="submit" value="Greet" /> </form> """
  • String substitution: This can be used to insert variable contents into messages on the screen as follows:
    <h4 class="container">
    Sorry, no results were found for "$(params(:search_movies))"
    </h4>
  • do block syntax: This syntax makes the code easier to read. Here is a simple example:
    list = [1, 2, 3]
    map(x -> x^2, list)

This can also be written as follows:

map(list) -> do x
    x^2
end
  • Here is an example usage from the Genie framework:
    route("/hello.html") do
     html("Hello World")
    end

Here, route() and html() are functions from the Genie framework. The do x syntax creates an anonymous function with argument x and passes it as the first argument to the function stated before do. In this example, the string "Hello World" will be shown on the web page when the /hello.html URL is requested.

Another way to write this, which is clearly not that readable, is as follows:

route(html("Hello World"), "/hello.html")

We will be using this do syntax quite often in Part 2.

  • &&: The Boolean and && operator is often used to write concise conditional code, for example:
    isempty(strip(params(:search_movies)))
    && redirect(:get_movies)

The preceding code first evaluates the isempty(strip(params(:search_movies))) part; if this is false, nothing happens anymore. Only if the first part is true will the second, redirect(:get_movies), get evaluated. Thus, if the search_movies parameter has a value, only then will the redirect to :get_movies take place.

  • ||: The or || operator doesn’t evaluate the part after || when the first part is true, for example:
    isa(getfield(m, field), Int) || return ValidationResult(invalid, :is_int, "should be an int")

In the preceding example, if m is of type Int, the ValidationResult from the right-hand side is not shown.

  • Pipe operator: This operator, denoted by |>, is quite handy. An example is as follows:
    h1("Welcome Admin") |> html

The output of the preceding function before the pipe operator (|>) is given as a first argument to the function after the pipe. This allows for easy function chaining.

  • Ternary form: An if condition a else b end statement is often written in a ternary form: condition ? a : b.

An example is as follows:

flash_has_message() ? """<div class="alert alert-$flashtype alert-dismissable">$(flash())</div>""" : ""

If the function flash_has_message() returns true, then the multiline string after ? (which contains the HTML code for a div) is the result of the expression; if false the empty string "" is returned.

  • Symbols: These are often used in Julia (web) code denoted by :, for example:
    julia> sym = :info
    :info
    julia> typeof(sym)
    Symbol

Symbols are used to indicate access to a variable (such as info), but what info exactly contains is not evaluated at that moment in code. A symbol gets replaced with the value bound to that symbol when the expression containing the symbol is evaluated at runtime.

Here is an example from Genie passing the values of the:user_id and :user_status variables in the payload to a createUser function:

createUser(postpayload(:user_id), postpayload(:user_status, "active"))

So, postpayload(:user_id) can be seen as binding values to variables (something that can change) inside user-provided expressions. Here, on evaluation, :user_id is replaced by the value Symbol is pointing to.

  • <% %>: Web frameworks often embed code inside HTML with <% %>. This can be used in the Julia Genie web framework, as follows:
    <h4><% movie.description %></h4>

In the preceding code, <% %> is used to insert a description field of a movie instance in that place.

<% %> can also contain whole blocks of code as well as function calls.

Embedded code can also contain a call to the @yield macro, like in the following snippet from app.jl.html in Genie:

<body>
<div class="container">
<%
@yield
%>
</div>
</body>

@yield is used to output the content of the view/controller into the layout.

Do familiarize yourself with the techniques described in this section. Doing so will make Julia web code instantly more understandable. All Julia web apps are projects that contain modules and use packages, so that’s what we’ll discuss in the next section.

 

Using Julia modules and packages

Code in Julia is not limited to functions and can be organized at higher levels through modules and packages. A module is one level higher than functions under which code in Julia can be organized. A package is another level higher, can contain one or more modules, and provides functionality that can be reused by other Julia projects. Often, a web app is a package, containing a number of modules. When the package contains a project file called Project.toml, the file is also a project.

Modules

Modules are used to group together the definitions of types, functions, constants, and so on that are related. By convention, a file named M.jl will always define a module named M.

Such a module will be declared as follows (shown here for the Genie framework):

module Genie
# Loads dependencies and bootstraps a Genie app.
# Exposes core Genie functionality.
end

And it will be stored in Genie.jl.

To illustrate, let’s create a module ToDoApp inside a ToDoApp.jl file, with the ToDo struct definition and a display function (see Chapter1\modules\ToDoApp.jl in the code repository):

module ToDoApp
using Dates   # to make the Date type available
export print_todo, ToDo
mutable struct ToDo
  id::Int32
  description::String
  completed::Bool
  created::Date
  priority::Int8
end
function print_todo(todo)
  if !todo.completed
    println("I still have to do: $(todo.description)")
    print("A todo created at: ")
    helper(todo)
  end
end
function helper(todo)
   println(todo.created)
end
end

In the preceding code, we see that using is needed to bring in the definitions of the Dates module.

In the REPL, we evaluate the preceding Julia script with include(" ToDoApp.jl"). Then, we employ using .ToDoApp.

The period (.) is used here because we want to look for definitions inside the scope of the current module. Without this, we get an error:

julia> using ToDoApp
ERROR: ArgumentError: Package ToDoApp not found in current path
Import

Also, we must do using Dates so that the Date type is recognized, which is needed when making a ToDo instance:

Now, let us define our struct instance as follows:

julia> todo1 = ToDo(1, "Getting groceries", false, Date("2022-04-01", "yyyy-mm-dd"), 5)
Main.ToDoApp.ToDo(1, "Getting groceries", false, Date("2022-04-01"), 5)

We can now call the exported print_todo function:

julia> print_todo(todo1)
I still have to do: Getting groceries
A todo created at: 2022-04-01

However, the helper function is not available because it was not exported from the module:

julia> helper(todo1)
ERROR: UndefVarError: helper not defined

But we can call the helper function as follows:

ToDoApp.helper(todo1) # => 2022-04-01

When evaluating using, Julia looks in the filesystem for modules or packages in paths that are stored in the LOAD_PATH variable. By default, LOAD_PATH contains the following:

julia> LOAD_PATH
3-element Vector{String}:
"@"
"@v#.#"
"@stdlib"

The preceding code implies that first the current project is searched, then the default Julia environment, and then the standard library.

The variable @__DIR__ contains the current folder. So, another way to enable Julia to search for modules or packages in the current folder is to say push!(LOAD_PATH, @__DIR__).

Let us summarize when and how to use using:

  • using MyPackage looks in the LOAD_PATH for a file called MyPackage.jl and loads the module contained in that file; all exported definitions are loaded into the current scope
  • using .MyPackage: This part of the code instructs looking for definitions inside the scope of the current module, which is needed because we have previously done include ("MyPackage.jl")
  • using ..MyPackage: This part of the code instructs looking for definitions inside the parent scope of the current module

Besides include and using, we can also bring in a module with import. Then, you have to prefix the name of a function or another object with its module name when it is used. For example, after import Inflector has imported the Inflector module, you have to use its to_plural function, as Inflector.to_plural(name).

The import keyword also has to be used when you want to extend functions with new methods. For example, if you want to pretty-print your own types with a new version of the show function, you first have to do import Base.show.

To bring in specific definitions, use : after using or import as follows:

import SearchLight: AbstractModel

As an example, here are the starting lines of the Genie module:

module Genie
import Inflector
include("Configuration.jl")
using .Configuration
const config = Configuration.Settings()
include("constants.jl")
import Sockets
import Logging
using Reexport
Using Revise
# rest of the code
end

Packages and projects

Packages are managed using the Git version control system and the package manager, Pkg (which is itself a package!). They are stored on GitHub, and each Julia package is named with a .jl suffix. A single GitHub repository may host one or more packages, but a good convention is one repository containing just one package.

A single package with the name P will always contain a P.jl file. By convention, this is placed in a subfolder, src. You can’t have other top-level modules in a single package.

As an example, the Genie framework, called Genie.jl, can be found at https://github.com/GenieFramework/Genie.jl.

A project is a package that contains two .toml files, which declare the packages your project depends on. You can create a project from the REPL as follows:

(@v1.8) pkg> generate MyPackage
Generating project MyPackage:
MyPackage\Project.toml
MyPackage\src/MyPackage.jl

The output will show the file structure created by the generate command.

The default Project.toml file contains the following:

name = "MyPackage"
uuid = "607adcac-db05-4b5b-9d7e-b11c396083d4"
authors = ["YourName<email-address>"]
version = "0.1.0"

A project can also contain a [deps] section, containing the names and universally unique ids (UUID) of the packages your project depends on (we will see an example of this in the next section). When adding a package to your project with the add command, the entry in the [deps] section is automatically filled in. The [compat] section constraints compatibility for the dependencies listed under [deps].

Besides Project.toml, a project can also have a manifest in the form of a Manifest.toml file, as indeed all Genie projects have. This file is generated and maintained by Pkg and, in general, should never be modified manually. The Manifest.toml file records the state of the packages in the current project environment, including exact information about (direct and indirect) dependencies of the project.

Given these two files, you can exactly reproduce the package dependency environment of a project, so this guarantees reproducibility.

Parsing a CSV file

As a simple example of how to work with packages, let’s read the data from a CSV file and display it. Suppose we have a todos.csv file that contains a header line with column names, and then line by line, the field data of our to-dos, as follows:

id, description, completed, created, priority
1, "Getting groceries", true, "2022-04-01", 5
2, "Visiting my therapist", false, "2022-04-02", 4
3, "Getting a haircut", true, "2022-03-28", 6
4, "Paying the energy bill", false, "2022-04-04", 8
5, "Blog on workspace management", true, "2022-03-29", 4
6, "Book a flight to Israel", false, "2022-04-04", 3
7, "Conquer the world", true, "2022-03-29", 1

Start up a REPL to work with the data. We already installed the CSV package previously in the Using the package mode to jump-start a project section. We’ll also need the DataFrames package to show our data as a table with columns, so go into pkg mode by typing ] and give the command: add DataFrames.

Going back to the normal REPL, type the following using command:

using CSV, DataFrames

Now, we can read in the CSV file into a DataFrame object, df, with the following command:

df = CSV.read("todos.csv", DataFrame)

You will get the following output in the REPL:

Figure 1.3 – Viewing a CSV file in a DataFrame

Figure 1.3 – Viewing a CSV file in a DataFrame

If the file has no header line, specify the header=false keyword argument. Also, if the data delimiter is something different, such as ;, you can specify this with delim=';'.

The CSV package has a lot more capabilities for reading and writing, which you can learn about here: https://csv.juliadata.org/stable/index.html.

Now that you’ve seen how to use modules, packages, and projects, let’s examine Julia’s internal workings a bit more.

 

How Julia works

After this whirlwind tour of Julia, we want to see why Julia is a good fit for the web world. To do that, we must have a good understanding of Julia’s internal workings.

The Julia JIT compiler works at runtime, starting from the Julia source code. Note that code from packages is most often precompiled. Type, method, and module definitions are written in an efficient serialized form so that the JIT can start compiling much faster. The first time a function is called with a certain combination of types of arguments, the correct machine code for those types is generated through LLVM. Moreover, the machine code is cached from then on, so after the initial compilation stage, the optimized code is looked up in the function’s vtable (see Types, flow controls, and functions), and you get the bonus of much-improved performance.

Julia apps will often be long-running processes, so there needs to be a mechanism for freeing memory resources. The Julia developer, however, is not burdened with this task. Julia has a garbage collector process. This is a simple mark-and-sweep GC causing low overhead. The best advice here is as follows:

  • Avoid unnecessary memory allocations
  • Use standard-library methods that modify variables (whose name ends with !, for example, sort!) instead of creating new ones
  • Use immutable objects (const and struct)
  • Pre-allocate enough memory from the start to avoid GC altogether

Now that we know somewhat better how Julia works, we can argue why Julia can be used for web development.

 

Why Julia is a good fit for web development

Web development using Julia is very popular because of the following:

  • The app needs to be deployed only on the server, not on an unknown number of clients, so the effort to put an application into production is minimal. This is true for all backend programming languages. To execute the app, you have the choice between installing a Julia runtime on your server or building a standalone executable with PackageCompiler.jl.
  • Powerful web frameworks exist. This is certainly the case for main-stream dynamic languages, for example, Ruby on Rails, Django (a Python framework), or Phoenix (an Elixir framework). But Julia also saw the development of powerful web frameworks in recent years (we will discuss this more in Part 2), especially the Genie framework.
  • Data scientists want to show the results of their research online in a visual and interactive fashion. Here, Julia, being a scientific and technical computing language, really shines.

Let’s look at some important app properties and evaluate how Julia performs at these so that it becomes clear why Julia is a nice fit for web backend development and for exposing web services:

  • Speed: Response speed is always of the utmost importance, no matter the kind of app you’re running. Commonly used programming languages to develop web applications, such as PHP, Ruby, and Python, are interpreted languages. Typically, they are compiled to bytecode, which is deployed to a production machine where it is run on a virtual machine (VM). This VM translates the bytecode to machine code. This causes apps written in these languages to fall behind in certain benchmarks, so performance can be an issue.

Julia on the other hand is known for its excellent execution speed, because of its JIT compiler and highly optimized machine code generated through LLVM. That’s why Julia often stays within the 2x range from optimized C code while outperforming dynamic languages with orders of magnitude (see https://julialang.org/benchmarks/ and https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/julia-python3.html).

Because of the JIT compiling stage, the startup of an application takes some time, which is sometimes called the JIT latency. So, often, the first execution of Julia code will take longer because execution waits for the compilation process. While this would be a problem for games or real-time apps, it is not an issue when developing web backends, which is a typically long-running process. Also, you can add a startup phase in which all your important code gets precompiled before exposing the app to users.

  • Garbage collection: The freeing of memory through the GC will be less noticeable on a server. Its effect is spread out over all client processes. See the previous How Julia works section, for some advice here.
  • Scalability: If your web app has an ever-growing number of users, you’ll likely run into problems sooner or later. Julia is designed for concurrent/parallel and distributed execution, which makes it highly scalable and thus particularly suited for running massive apps with many users. If your web application requires heavy calculations or can come under a heavy load, Julia will make a great difference.
  • Platforms: Because LLVM is used under the hood, Julia runs on very diverse platforms (see https://julialang.org/downloads/#currently_supported_platforms).
  • Functional: Julia’s emphasis on functions makes Julia a good choice for developing web services, which are typically function-oriented.

An ecosystem of libraries: Another of Julia’s advantages over competing languages is that libraries can be combined and extended very easily. This allows for more code reuse, which means less time and effort is needed in Julia to develop a web app as in the competing language frameworks. Moreover, all Python libraries can be used via PyCall.jl, so in case no existing Julia package meets your need, you can use an appropriate Python library. The same goes for R with Rcall.jl, and Java/Scala with JavaCall.jl. Also, Julia can call C code directly, without any libraries needed.

We can conclude that Julia’s performance and scalability characteristics and its extensive number of packages for visualizing data make it an excellent fit for the development of web apps, web services, and web dashboards.

 

Summary

In this chapter, we reviewed the Julia programming language in order to prepare ourselves for web development with Julia.

We worked with Julia in the REPL and with the VS Code editor, which is how we’ll build web apps in the rest of the book. Then, we looked at types, flow controls, functions, and methods, which you’ll need in any Julia app.

We followed that up with some useful Julia techniques in web development. We discussed modules and packages and illustrated them using the CSV and DataFrames packages.

Finally, we covered how the Julia runtime works and why Julia is a good fit for web development.

By now, you should be able to understand the underlying mechanisms of the code in future chapters and how to use Julia in your own projects.

In the next chapter, we’ll dive into what Julia’s standard library and JuliaWeb have to offer for building web apps.

 

Further reading

If you need a more thorough introduction or more details, follow a tutorial on https://julialang.org/learning/tutorials/, visit the Julia documentation at https://docs.julialang.org/en/v1/, or choose a book from https://julialang.org/learning/books/.

About the Authors
  • Ivo Balbaert

    Ivo Balbaert is a lecturer in web programming and databases at CVO Antwerpen, a community college in Belgium. He received a PhD in applied physics from the University of Antwerp in 1986. He worked for 20 years in the software industry as a developer and consultant in several companies, and for 10 years as project manager at the University Hospital of Antwerp. From 2000 onwards, he switched to partly teaching and partly developing software (at KHM Mechelen, CVO Antwerpen). He also wrote an introductory book in Dutch about developing in Ruby and Rails, Programmeren met Ruby en Rails, published by Van Duuren Media. In 2012, he authored a book on the Go programming language, The Way to Go, published by iUniverse. He has written a number of introductory books for new programming languages, notably Dart, Julia, Rust, and Red, all published by Packt.

    Browse publications by this author
  • Adrian Salceanu

    Adrian Salceanu is the creator and lead maintainer of Genie Framework. He has over two decades of professional work experience as a web developer and software architect, leading agile teams in developing, scaling, and maintaining business critical, data-intensive web applications. Currently, he is the technical founder and CEO of Genie Cloud, a no-code app development platform built with Genie. Adrian is the author of Julia Programming Projects (published by Packt in 2018) and an enthusiastic JuliaLang open-source contributor. He has two master's degrees, one in Computing, and another in Advanced Computer Science.

    Browse publications by this author
Web Development with Julia and Genie
Unlock this book and the full library FREE for 7 days
Start now