module App
using GenieFramework
# @genietools loads every GenieFramework component in one shot
# no further imports or init calls are needed
@genietools
# Define reactive input and output variables
# and their dependency relationships
@app begin
@in name::String = "Genie"
@out message::String = "Genie"
@onchange name begin
message = "$(name)"
end
end
# Define the frontend UI
function ui()
[
h1("My first Genie app")
textfield("Enter your name", :name)
p("Hello {{message}}!")
]
end
# Define routers
# Map URIs to their handlers (functions), respectively
@page("/", ui)
end1 Introduction
The open source Genie framework has three main components:
Genie.jl: delivers full-stack backend and frontend tooling for building web applications and APIs.Stipple.jl: supplies a reactive UI layer that automatically syncs data between client and server.SearchLight.jl: handles database persistence with an integrated ORM.Plugins: extend the capabilities of
Genie.jl,Stipple.jl, orSearchLight.jl.Genie Builder: a closed-source VS Code extension that adds drag-and-drop, no-code UI design to the Genie ecosystem.
注:
ORM,即 Object-Relational Mapping,中文译为“对象-关系映射”,其作用是在关系型数据库(Relational Database)与面向对象语言之间建立一条“自动翻译”的通道,前台的对象型数据(对象、属性、方法)和数据库中的关系型数据(表、行、列、SQL)通过这个通道来相互转换,即能用操作编程语言对象的方式来读写数据库,而不用写SQL。
关系型数据库(Relational Database)是以“表(Table)”为核心来存储数据的数据库,具有以下特点:
表:二维行列结构。一行是一条 record,一列是一个 field,每列有固定的类型。
Primary key:每行必须有一个唯一标识。
Foregin key:让 A 表的某列引用 B 表的 primary key,从而建立“关系”。
用 SQL(Structured Query Language)统一操作表。
ACID 事务保证:atomicity(原子性):事务中的操作要么全做,要么全不做,即任何一步失败,整个事务回滚;consistency(一致性):事务完成后,数据库必须处于“合法状态”,即所有定义好的规则都不能被破坏;Isolation(隔离性):并发事务之间相互隔离,互不干扰,就像串行执行一样;durability(持久性):一旦事务完成,数据就永久保存。
1.1 A simple Genie web app
- Create a new folder for your project.
mkdir MyGenieApp
cd MyGenieApp
- Start Julia with the current project activited.
julia --project=.
- In the Julia REPL, enter the
Pkgmode by typing], and then installGenieFramework.jl(this will installGenie.jlandStipple.jlas well as their dependencies).
(MyGenieApp) pkg> add GenieFramework
- Create a file named
app.jlwith the following content.
- Exit
Pkgmode by pressingBackspace, and then in the Julia REPL, load and run your app.
using GenieFramework
# Load your app
Genie.loadapp()
# Run your app in http://localhost:8000 by default
up()- Open a web browser and go to
http://localhost:8000. You should see your"Hello Genie!"message.
2 Genie.jl
2.1 Introduction
Genie.jl is the core of the Genie framework.
(@v1.11) pkg> add Genie
2.2 A simple example
Create a new file:
touch("example.jl")
edit("example.jl")Add the following code:
using Genie, Genie.Renderer, Genie.Renderer.Html, Genie.Renderer.Json
route("/hello.html") do
html("Hello World")
end
route("/hello.json") do
json("Hello World")
end
route("/hello.txt") do
# For MIME types or response types not provided by Genie,
# users can register their own mime types and response types as needed,
# or can pass the full mime type as a string such as "text/csv"
respond("Hello World", :text)
end
# Block the execution of the script by passing the `async=false` argument
# This way we make sure that the script stays running
# otherwise, at the end of the script, the Julia process would normally exit,
# killing the server
up(8001, async=false)Launch the script:
julia ./example.jl
2.3 Routing
Genie’s router can be considered as the brain of the app, matching web requests to handler functions, extracting and setting up the request’s variables and the execution environment, and invoking the response methods.
2.3.1 Static routing
The router supports two ways of registering routes, either route(pattern::String, f::Function) or route(f::Function, pattern::String). The first syntax is for passing function references, while the second is for defining inline functions (lambdas).
Pass a function reference to route:
using Genie
greet() = "Welcome to Genie!"
route("/greet", greet)
up()Define an inline function:
route("/bye") do
"Good bye!"
endThe routes are matched from newest to oldest. This means that you can define a new route to overwrite a previously defined one.
Genie’s router won’t match the most specific rule, but the first matching one.
2.4 Dynamic routing - using routing parameters
We can define a dynamic route using routing parameters (:<variable name>). Upon matching the request, the router will unpack the values and expose them in the params collection.
using Genie, Genie.Requests
route("/customers/:customer_id/orders/:order_id") do
"You asked for the order $(payload(:order_id)) for customer $(payload(:customer_id))"
end
up()2.5 Routing methods
By default, routes handle GET requests. In order to define routes for handling other types of request methods, we need to pass the method keyword argument.
Genie’s router supports GET, POST, PUT, PATCH, DELETE, and OPTIONS methods. The router defines and exports constants for each of these as Router.GET, Router.POST, Router.PUT, Router.PATCH, Router.DELETE, and Router.OPTIONS.
using Genie, Genie.Requests
route("/patch_stuff", method=PATCH) do
"Stuff to patch"
end
up()And we can test it using the HTTP package:
using HTTP
HTTP.request("PATCH", "http://localhost:8000/patch_stuff").body |> String