mirror of
https://github.com/vlang/v.git
synced 2025-08-03 09:47:15 -04:00
veb: deprecate vweb (part 1); update the blog tutorial
This commit is contained in:
parent
8dbde185ef
commit
f83af8a1a2
@ -4,7 +4,7 @@ import os
|
||||
import log
|
||||
import flag
|
||||
import time
|
||||
import vweb
|
||||
import veb
|
||||
import net.urllib
|
||||
|
||||
// This tool regenerates V's bootstrap .c files
|
||||
@ -79,11 +79,14 @@ mut:
|
||||
|
||||
// webhook server
|
||||
struct WebhookServer {
|
||||
vweb.Context
|
||||
mut:
|
||||
gen_vc &GenVC = unsafe { nil } // initialized in init_server
|
||||
}
|
||||
|
||||
struct Context {
|
||||
veb.Context
|
||||
}
|
||||
|
||||
// storage for flag options
|
||||
struct FlagOptions {
|
||||
work_dir string
|
||||
@ -116,7 +119,8 @@ fn main() {
|
||||
}
|
||||
// webhook server mode
|
||||
if flag_options.serve {
|
||||
vweb.run[WebhookServer](&WebhookServer{}, flag_options.port)
|
||||
mut server := &WebhookServer{}
|
||||
veb.run_at[WebhookServer, Context](mut server, port: flag_options.port)!
|
||||
} else {
|
||||
// cmd mode
|
||||
mut gen_vc := new_gen_vc(flag_options)
|
||||
@ -152,7 +156,7 @@ pub fn (mut ws WebhookServer) index() {
|
||||
}
|
||||
|
||||
// gen webhook
|
||||
pub fn (mut ws WebhookServer) genhook() {
|
||||
pub fn (mut ws WebhookServer) genhook() veb.Result {
|
||||
// request data
|
||||
// println(ws.vweb.req.data)
|
||||
// TODO: parse request. json or urlencoded
|
||||
@ -160,10 +164,9 @@ pub fn (mut ws WebhookServer) genhook() {
|
||||
ws.gen_vc.generate()
|
||||
// error in generate
|
||||
if ws.gen_vc.gen_error {
|
||||
ws.json('{status: "failed"}')
|
||||
return
|
||||
return ctx.json('{status: "failed"}')
|
||||
}
|
||||
ws.json('{status: "ok"}')
|
||||
return ctx.json('{status: "ok"}')
|
||||
}
|
||||
|
||||
pub fn (ws &WebhookServer) reset() {
|
||||
|
@ -1,28 +1,24 @@
|
||||
## Building a 400 KB web blog in V & SQLite
|
||||
# Building a 400 KB Web Blog in V & SQLite with Veb
|
||||
|
||||
Hello,
|
||||
|
||||
In this guide, we'll build a simple web blog in V.
|
||||
In this guide, we'll build a simple web blog using V and the Veb framework.
|
||||
|
||||
The benefits of using V for web:
|
||||
The benefits of using V and Veb for web development:
|
||||
|
||||
- A safe, fast, language with the development agility of Python or Ruby and
|
||||
the performance of C.
|
||||
- Zero dependencies: everything you need for web development comes with the language
|
||||
in a 1 MB package.
|
||||
- Very small resulting binaries: the blog we'll create in this tutorial is about 150 KB.
|
||||
- Easy deployments: a single binary file that even includes the precompiled templates.
|
||||
- Runs on the cheapest hardware with minimum footprint: for most apps a $3 instance
|
||||
is enough.
|
||||
- Fast development without any boilerplate.
|
||||
- A safe, fast language with the development agility of Python or Ruby and the performance of C
|
||||
- Zero dependencies: everything needed comes in a 1 MB package
|
||||
- Very small binaries: this blog will be about 150 KB
|
||||
- Easy deployments: single binary including precompiled templates
|
||||
- Runs on minimal hardware: $3 instance sufficient for most apps
|
||||
- Fast development with minimal boilerplate
|
||||
|
||||
> [!NOTE]
|
||||
> V and Vweb are at a very early stage and are changing rapidly.
|
||||
> V and Veb are at an early stage and evolving rapidly.
|
||||
|
||||
The code is available [here](./code/blog).
|
||||
|
||||
### Installing V
|
||||
|
||||
## Installing V
|
||||
```
|
||||
wget --quiet https://github.com/vlang/v/releases/latest/download/v_linux.zip
|
||||
unzip v_linux.zip
|
||||
@ -30,21 +26,20 @@ cd v
|
||||
sudo ./v symlink
|
||||
```
|
||||
|
||||
Now V should be globally available on your system.
|
||||
|
||||
V should now be globally available.
|
||||
|
||||
> On macOS use `v_macos.zip`, on Windows - `v_windows.zip`.
|
||||
> If you use a BSD system, Solaris, Android, or simply want to install V
|
||||
> from source, follow the simple instructions here:
|
||||
> For BSD, Solaris, Android, or source installation, see:
|
||||
> https://github.com/vlang/v#installing-v-from-source
|
||||
|
||||
### Install SQLite development dependency
|
||||
## Install SQLite Development Dependency
|
||||
|
||||
If you don't have it already installed, look at the
|
||||
[`sqlite` README](../../vlib/db/sqlite/README.md) for instructions.
|
||||
See [`sqlite` README](../../vlib/db/sqlite/README.md) for instructions if not already installed.
|
||||
|
||||
### Creating a new Vweb project
|
||||
## Creating a New Veb Project
|
||||
|
||||
V projects can be created anywhere and don't need to have a certain structure:
|
||||
V projects can be created anywhere:
|
||||
|
||||
```bash
|
||||
mkdir blog
|
||||
@ -58,20 +53,21 @@ First, let's create a simple hello world website:
|
||||
// blog.v
|
||||
module main
|
||||
|
||||
import vweb
|
||||
import veb
|
||||
|
||||
struct App {
|
||||
vweb.Context
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
}
|
||||
|
||||
pub struct App {}
|
||||
|
||||
fn main() {
|
||||
app := App{}
|
||||
vweb.run(app, 8081)
|
||||
mut app := &App{}
|
||||
veb.run[App, Context](mut app, 8081)
|
||||
}
|
||||
|
||||
@['/index']
|
||||
pub fn (mut app App) index() vweb.Result {
|
||||
return app.text('Hello world from vweb!')
|
||||
pub fn (app &App) index(mut ctx Context) veb.Result {
|
||||
return ctx.text('Hello world from Veb!')
|
||||
}
|
||||
```
|
||||
|
||||
@ -82,17 +78,20 @@ v run blog.v
|
||||
```
|
||||
|
||||
```
|
||||
Running a Vweb app on http://localhost:8081 ...
|
||||
Running a Veb app on http://localhost:8081 ...
|
||||
```
|
||||
|
||||
Vweb helpfully provided a link, open http://localhost:8081/ in your browser:
|
||||
Veb helpfully provided a link, open http://localhost:8081/ in your browser:
|
||||
|
||||
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_vweb/img/hello.png?raw=true">
|
||||
|
||||
The `App` struct is an entry point of our web application. If you have experience
|
||||
with an MVC web framework, you can think of it as a controller. (Vweb is
|
||||
not an MVC framework however.) It embeds the vweb Context object, that's why we get access
|
||||
to methods like `.text()`.
|
||||
The App struct holds shared application data, while Context handles per-request data and embeds
|
||||
`veb.Context` for response methods like `.text()`.
|
||||
|
||||
If you have experiencewith an MVC web framework, you can think of it as a controller. (Veb is
|
||||
not an MVC framework however.)
|
||||
|
||||
|
||||
|
||||
As you can see, there are no routing rules. The `index()` action handles the `/` request by default.
|
||||
Vweb often uses convention over configuration and adding a new action requires
|
||||
@ -100,14 +99,16 @@ no routing rules either:
|
||||
|
||||
```v oksyntax
|
||||
// blog.v
|
||||
import vweb
|
||||
import veb
|
||||
import time
|
||||
|
||||
fn (mut app App) time() vweb.Result {
|
||||
fn (mut app App) time() veb.Result {
|
||||
return app.text(time.now().format())
|
||||
}
|
||||
```
|
||||
|
||||
Custom routes can be defined using attributes like @['/index'].
|
||||
|
||||
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_vweb/img/time.png?raw=true">
|
||||
|
||||
> TIP: run the following command to live-reload the server: `v watch run blog.v`
|
||||
@ -122,12 +123,12 @@ Let's return an HTML view instead. Create `index.html` in the same directory:
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>V Blog</title>
|
||||
<title>V Blog</title>
|
||||
</head>
|
||||
<body>
|
||||
<b>@message</b>
|
||||
<br />
|
||||
<img src="https://vlang.io/img/v-logo.png" width="100" />
|
||||
<b>@message</b>
|
||||
<br />
|
||||
<img src="https://vlang.io/img/v-logo.png" width="100" />
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@ -136,9 +137,9 @@ and update our `index()` action so that it returns the HTML view we just created
|
||||
|
||||
```v ignore
|
||||
// blog.v
|
||||
pub fn (mut app App) index() vweb.Result {
|
||||
pub fn (mut app App) index() veb.Result {
|
||||
message := 'Hello, world from Vweb!'
|
||||
return $vweb.html()
|
||||
return $veb.html()
|
||||
}
|
||||
```
|
||||
|
||||
@ -162,7 +163,7 @@ to modify any data from a view. `<b>@foo.bar()</b>` will only work if the `bar()
|
||||
doesn't modify `foo`.
|
||||
|
||||
The HTML template is compiled to V during the compilation of the website,
|
||||
that's done by the `$vweb.html()` line.
|
||||
that's done by the `$veb.html()` line.
|
||||
(`$` always means compile time actions in V.) offering the following benefits:
|
||||
|
||||
- Great performance, since the templates don't need to be compiled
|
||||
@ -185,12 +186,12 @@ Add a SQLite handle to `App`:
|
||||
```v oksyntax
|
||||
// blog.v
|
||||
import db.sqlite
|
||||
import vweb
|
||||
import veb
|
||||
|
||||
struct App {
|
||||
vweb.Context
|
||||
pub mut:
|
||||
// ...
|
||||
db sqlite.DB
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
@ -202,7 +203,7 @@ since a DB connection doesn't have to be set up for each request.
|
||||
```v oksyntax
|
||||
// blog.v
|
||||
fn main() {
|
||||
mut app := App{
|
||||
mut app := &App{
|
||||
db: sqlite.connect(':memory:')!
|
||||
}
|
||||
sql app.db {
|
||||
@ -223,7 +224,7 @@ fn main() {
|
||||
insert first_article into Article
|
||||
insert second_article into Article
|
||||
}!
|
||||
vweb.run(app, 8080)
|
||||
veb.run[App, Context](mut app, 8081)
|
||||
}
|
||||
```
|
||||
|
||||
@ -254,9 +255,9 @@ Let's fetch the articles in the `index()` action:
|
||||
|
||||
```v ignore
|
||||
// blog.v
|
||||
pub fn (app &App) index() vweb.Result {
|
||||
pub fn (app &App) index() veb.Result {
|
||||
articles := app.find_all_articles()
|
||||
return $vweb.html()
|
||||
return $veb.html()
|
||||
}
|
||||
```
|
||||
|
||||
@ -266,8 +267,8 @@ Finally, let's update our view:
|
||||
<body>
|
||||
@for article in articles
|
||||
<div>
|
||||
<b>@article.title</b> <br />
|
||||
@article.text
|
||||
<b>@article.title</b> <br />
|
||||
@article.text
|
||||
</div>
|
||||
@end
|
||||
</body>
|
||||
@ -309,7 +310,7 @@ bad queries will always be handled by the developer:
|
||||
```v ignore
|
||||
// article.v
|
||||
article := app.retrieve_article() or {
|
||||
return app.text('Article not found')
|
||||
return ctx.text('Article not found')
|
||||
}
|
||||
```
|
||||
|
||||
@ -320,40 +321,45 @@ Create `new.html`:
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>V Blog</title>
|
||||
<title>V Blog</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/new_article" method="post">
|
||||
<input type="text" placeholder="Title" name="title" /> <br />
|
||||
<textarea placeholder="Text" name="text"></textarea>
|
||||
<input type="submit" />
|
||||
</form>
|
||||
<form action="/new_article" method="post">
|
||||
<input type="text" placeholder="Title" name="title" /> <br />
|
||||
<textarea placeholder="Text" name="text"></textarea>
|
||||
<input type="submit" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```v ignore
|
||||
// article.v
|
||||
import vweb
|
||||
@['/new']
|
||||
pub fn (app &App) new(mut ctx Context) veb.Result {
|
||||
return $veb.html()
|
||||
}
|
||||
|
||||
@['/new_article'; post]
|
||||
pub fn (app &App) new_article(mut ctx Context) veb.Result {
|
||||
title := ctx.form['title'] or { '' }
|
||||
text := ctx.form['text'] or { '' }
|
||||
|
||||
@[post]
|
||||
pub fn (mut app App) new_article(title string, text string) vweb.Result {
|
||||
if title == '' || text == '' {
|
||||
return app.text('Empty text/title')
|
||||
return ctx.text('Empty text/title')
|
||||
}
|
||||
|
||||
article := Article{
|
||||
title: title
|
||||
text: text
|
||||
}
|
||||
println(article)
|
||||
sql app.db {
|
||||
insert article into Article
|
||||
} or { panic(err) }
|
||||
return app.redirect('/')
|
||||
return ctx.redirect('/')
|
||||
}
|
||||
```
|
||||
|
||||
The decorator on our function tells vweb that it is an HTTP POST type operation.
|
||||
The decorator on our function tells Veb that it is an HTTP POST type operation.
|
||||
|
||||
This time Vweb parses the HTTP form and assigns correct values with correct types to
|
||||
function arguments, which saves a lot of typing (e.g. `title := app.form['title']` is
|
||||
@ -369,8 +375,8 @@ Next we need to add the HTML endpoint to our code like we did with `index.html`:
|
||||
|
||||
```v ignore
|
||||
@['/new']
|
||||
pub fn (mut app App) new() vweb.Result {
|
||||
return $vweb.html()
|
||||
pub fn (mut app App) new() veb.Result {
|
||||
return $veb.html()
|
||||
}
|
||||
```
|
||||
|
||||
@ -383,11 +389,10 @@ to render everything on the client or need an API, creating JSON endpoints
|
||||
in V is very simple:
|
||||
|
||||
```v oksyntax
|
||||
// article.v
|
||||
import vweb
|
||||
import veb
|
||||
|
||||
@['/articles'; get]
|
||||
pub fn (mut app App) articles() vweb.Result {
|
||||
pub fn (mut app App) articles() veb.Result {
|
||||
articles := app.find_all_articles()
|
||||
return app.json(articles)
|
||||
}
|
||||
@ -417,10 +422,10 @@ Run
|
||||
v -d use_openssl -o blog -prod . && strip ./blog
|
||||
```
|
||||
|
||||
This will result in a ~400KB binary. `-d use_openssl` tells vweb to link to OpenSSL.
|
||||
This will result in a ~400KB binary. `-d use_openssl` tells Veb to link to OpenSSL.
|
||||
Without this flag mbedtls will be embedded, and the binary size will increase to ~700KB.
|
||||
|
||||
|
||||
### To be continued...
|
||||
|
||||
For an example of a more sophisticated web app written in V, check out Vorum: https://github.com/vlang/vorum
|
||||
For an example of a more sophisticated web app written in V, check out Gitly: https://github.com/vlang/gitly
|
||||
|
@ -1,78 +1,90 @@
|
||||
module main
|
||||
|
||||
import vweb
|
||||
import veb
|
||||
import time
|
||||
import db.sqlite
|
||||
import json
|
||||
|
||||
struct App {
|
||||
vweb.Context
|
||||
// Context struct must embed veb.Context
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
pub mut:
|
||||
db sqlite.DB
|
||||
user_id string
|
||||
}
|
||||
|
||||
// App struct for shared data
|
||||
pub struct App {
|
||||
pub mut:
|
||||
db sqlite.DB
|
||||
}
|
||||
|
||||
// Main function
|
||||
fn main() {
|
||||
mut app := App{
|
||||
mut app := &App{
|
||||
db: sqlite.connect('blog.db') or { panic(err) }
|
||||
}
|
||||
sql app.db {
|
||||
create table Article
|
||||
}!
|
||||
vweb.run(app, 8081)
|
||||
// Use veb.run with App and Context types
|
||||
veb.run[App, Context](mut app, 8081)
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn (mut app App) index_text() vweb.Result {
|
||||
app.vweb.text('Hello, world from vweb!')
|
||||
return vweb.Result{}
|
||||
}
|
||||
|
||||
pub fn (app &App) index_html() vweb.Result {
|
||||
message := 'Hello, world from Vweb!'
|
||||
return $vweb.html()
|
||||
// Middleware to run before each request
|
||||
pub fn (mut ctx Context) before_request() bool {
|
||||
ctx.user_id = ctx.get_cookie('id') or { '0' }
|
||||
return true
|
||||
}
|
||||
*/
|
||||
|
||||
// Index endpoint
|
||||
@['/index']
|
||||
pub fn (app &App) index() vweb.Result {
|
||||
pub fn (app &App) index(mut ctx Context) veb.Result {
|
||||
articles := app.find_all_articles()
|
||||
return $vweb.html()
|
||||
}
|
||||
|
||||
pub fn (mut app App) before_request() {
|
||||
app.user_id = app.get_cookie('id') or { '0' }
|
||||
return $veb.html()
|
||||
}
|
||||
|
||||
// New article form endpoint
|
||||
@['/new']
|
||||
pub fn (mut app App) new() vweb.Result {
|
||||
return $vweb.html()
|
||||
pub fn (app &App) new(mut ctx Context) veb.Result {
|
||||
return $veb.html()
|
||||
}
|
||||
|
||||
// Create new article endpoint
|
||||
@['/new_article'; post]
|
||||
pub fn (mut app App) new_article(title string, text string) vweb.Result {
|
||||
pub fn (app &App) new_article(mut ctx Context) veb.Result {
|
||||
title := ctx.form['title'] or { '' }
|
||||
text := ctx.form['text'] or { '' }
|
||||
|
||||
if title == '' || text == '' {
|
||||
return app.text('Empty text/title')
|
||||
return ctx.text('Empty text/title')
|
||||
}
|
||||
|
||||
article := Article{
|
||||
title: title
|
||||
text: text
|
||||
}
|
||||
println('posting article')
|
||||
println(article)
|
||||
|
||||
sql app.db {
|
||||
insert article into Article
|
||||
} or {}
|
||||
|
||||
return app.redirect('/')
|
||||
return ctx.redirect('/')
|
||||
}
|
||||
|
||||
// Get all articles endpoint
|
||||
@['/articles'; get]
|
||||
pub fn (mut app App) articles() vweb.Result {
|
||||
pub fn (app &App) articles(mut ctx Context) veb.Result {
|
||||
articles := app.find_all_articles()
|
||||
json_result := json.encode(articles)
|
||||
return app.json(json_result)
|
||||
return ctx.json(json_result)
|
||||
}
|
||||
|
||||
fn (mut app App) time() {
|
||||
app.text(time.now().format())
|
||||
// Time endpoint
|
||||
@['/time']
|
||||
pub fn (app &App) time(mut ctx Context) veb.Result {
|
||||
return ctx.text(time.now().format())
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<title>V Blog</title>
|
||||
</head>
|
||||
<body>
|
||||
user id = @app.user_id <br>
|
||||
user id = @ctx.user_id <br>
|
||||
@for article in articles
|
||||
<div>
|
||||
<b>@article.title</b> <br>
|
||||
|
Loading…
x
Reference in New Issue
Block a user