📚 Tutorial cho người mới · 2026

Học lập trình với GracePL

Hướng dẫn từng bước từ zero đến hero — dù bạn chưa bao giờ lập trình, hay đến từ PHP/Python/JavaScript, bạn đều có thể bắt đầu ở đây.

Tiến độ bài học
0 / 17 bài đã xem

Bài 1 GracePL là gì?

🎯 Mục tiêu bài này: Hiểu GracePL là gì, tại sao nên học, và nó khác gì so với PHP/Python/JavaScript.

GracePL là ngôn ngữ lập trình production-ready được thiết kế để:

  • cú pháp quen thuộc như PHP — dễ học, dễ đọc
  • Chạy nhanh như Go — biên dịch sang bytecode, thực thi bởi VM viết bằng Go
  • Tích hợp sẵn hơn 335 hàm — HTTP, Database, Redis, JWT, Crypto, WebSocket... không cần cài gói ngoài
  • Hỗ trợ concurrency thực sự (goroutine + channel như Go)

So sánh nhanh

Tính năngPHPPythonGracePL
Cú pháp✅ Quen thuộc✅ Đơn giản✅ Giống PHP
Tốc độ⚠️ Trung bình⚠️ Trung bình✅ Nhanh (Go VM)
HTTP server tích hợp⚠️ Flask/FastAPI✅ Có sẵn
Database tích hợp❌ PDO/MySQLi❌ SQLAlchemy✅ Có sẵn
Concurrency⚠️ asyncio✅ Goroutine+Channel
Cài đặt⚠️ Phức tạp⚠️ pip packages✅ 1 file executable
💡 Bạn đã biết PHP? Tuyệt vời! GracePL dùng $biến, function, foreach, class — bạn sẽ cảm thấy như ở nhà chỉ sau 5 phút.
✅ Đánh dấu đã học xong

Bài 2 Cài đặt GracePL

🎯 Mục tiêu: Cài GracePL và chạy được lệnh gracepl trong terminal.

Bước 1: Tải về

bash
# Tải binary từ GitHub Releases
# Windows: gracepl.exe
# Linux/macOS: gracepl

# Hoặc build từ source (cần Go 1.21+)
git clone https://github.com/ghoullp/gracepl-lang
cd gracepl-lang
go build -o gracepl .

Bước 2: Thêm vào PATH

bash — Linux/macOS
sudo mv gracepl /usr/local/bin/
gracepl --version
powershell — Windows
# Copy gracepl.exe tới một thư mục trong PATH, ví dụ C:\tools\
.\gracepl.exe --version

Bước 3: Cài VS Code Extension (khuyến nghị)

Tìm kiếm "GracePL" trong VS Code Extension Marketplace để có:

  • Syntax highlighting
  • Code completion (IntelliSense)
  • Tích hợp debugger
  • Run script bằng phím F5
✅ Kiểm tra thành công: Chạy gracepl --version trong terminal — nếu thấy số version là xong!
✅ Đánh dấu đã học xong

Bài 3 Hello World — Chương trình đầu tiên

🎯 Mục tiêu: Tạo và chạy file GracePL đầu tiên của bạn.

Tạo file hello.gp

Mọi file GracePL bắt đầu bằng thẻ mở <gp> — tương tự <?php trong PHP.

hello.gp
<gp>
echo "Xin chào thế giới!\n";
echo "Tôi đang học GracePL 🚀\n";

// Biến và nội suy chuỗi
$ten = "Alice";
$tuoi = 25;
echo "Tên: $ten, Tuổi: $tuoi\n";

// Cũng có thể dùng print
$pi = 3.14159;
echo "Pi = $pi\n";

Chạy script

terminal
gracepl run hello.gp
📝 Kết quả mong đợi
Xin chào thế giới!
Tôi đang học GracePL 🚀
Tên: Alice, Tuổi: 25
Pi = 3.14159
💡 Chú ý: \n là ký tự xuống dòng (newline). Nếu muốn in không xuống dòng, bỏ \n đi. Bạn cũng có thể dùng println("text") — tự động thêm xuống dòng.
✅ Đánh dấu đã học xong

Bài 4 Các lệnh CLI cơ bản

LệnhMô tả
gracepl run script.gpChạy script
gracepl run script.gp -- arg1 arg2Chạy với arguments
gracepl check script.gpKiểm tra lỗi cú pháp không chạy
gracepl debug script.gpChạy với VS Code debugger (DAP)
gracepl test ./tests/Chạy tất cả test trong thư mục
gracepl pkg install nameCài package
gracepl replChế độ tương tác (REPL)

REPL — Thử code trực tiếp

REPL (Read-Eval-Print Loop) là cách học nhanh nhất — gõ code và xem kết quả ngay:

terminal
$ gracepl repl
gracepl> $x = 10 + 20
gracepl> echo $x      // 30
gracepl> strlen("hello")  // 5
gracepl> exit
✅ Đánh dấu đã học xong

Bài 5 Biến & Kiểu dữ liệu

🎯 Mục tiêu: Hiểu các kiểu dữ liệu cơ bản và cách khai báo biến.

Biến trong GracePL bắt đầu bằng ký tự $. Không cần khai báo kiểu — ngôn ngữ tự nhận diện.

types.gp
<gp>
// ── Số nguyên (int) ──────────────────────────
$tuoi = 25;
$so_am = -10;
$hexa = 0xFF;         // 255 — hệ thập lục phân

// ── Số thực (float) ──────────────────────────
$pi = 3.14159;
$gia = 99.99;

// ── Chuỗi (string) ───────────────────────────
$ten = "Alice";
$chao = 'Xin chào';  // nháy đơn không nội suy biến
$msg = "Tên tôi là $ten"; // nháy kép: nội suy $ten vào chuỗi

// ── Boolean ─────────────────────────────────
$dung = true;
$sai  = false;

// ── Null ─────────────────────────────────────
$rong = null;

// ── Kiểm tra kiểu ───────────────────────────
echo gettype($tuoi);  // "integer"
echo gettype($pi);    // "double"
echo gettype($ten);   // "string"
echo gettype($dung);  // "boolean"
echo gettype($rong);  // "NULL"

echo is_int($tuoi);     // true
echo is_float($pi);     // true
echo is_string($ten);   // true
echo is_null($rong);    // true

// ── Chuyển kiểu ─────────────────────────────
$str_num = "42";
$so = intval($str_num);    // → 42 (int)
$fl  = floatval("3.14");  // → 3.14 (float)
$st  = strval(123);       // → "123" (string)

Phạm vi biến (Scope)

scope.gp
<gp>
$toan_cuc = "Tôi ở ngoài function";

function sayHello() {
    // ✅ Closure capture: dùng use để truy cập biến ngoài
    echo "Hello!\n";
    // KHÔNG thể truy cập $toan_cuc trực tiếp
}

// Dùng closure với use để capture biến ngoài
$greet = function($ten) use ($toan_cuc) {
    echo "Xin chào $ten! ($toan_cuc)\n";
};
$greet("Bob");
✅ Đánh dấu đã học xong

Bài 6 Toán tử

operators.gp
<gp>
// ── Số học ───────────────────────────────────
$a = 10; $b = 3;
echo $a + $b;   // 13
echo $a - $b;   // 7
echo $a * $b;   // 30
echo $a / $b;   // 3.333...
echo $a % $b;   // 1   (chia lấy dư)
echo $a ** $b;  // 1000 (lũy thừa)
echo intdiv($a, $b); // 3 (chia nguyên)

// ── Gán kết hợp ──────────────────────────────
$x = 5;
$x += 3;  // $x = 8
$x -= 2;  // $x = 6
$x *= 4;  // $x = 24
$x /= 3;  // $x = 8
$x %= 3;  // $x = 2
$x .= "abc"; // Nối chuỗi: "2abc"

// ── So sánh ──────────────────────────────────
echo (5 == "5");   // true  (chỉ so giá trị)
echo (5 === "5");  // false (so giá trị VÀ kiểu)
echo (5 != 3);    // true
echo (5 !== "5"); // true
echo (5 > 3);     // true
echo (5 >= 5);    // true
echo (3 < 5);     // true
echo (3 <= 3);    // true

// ── Logic ────────────────────────────────────
echo (true && false);  // false  (VÀ)
echo (true || false);  // true   (HOẶC)
echo (!true);          // false  (PHỦ ĐỊNH)
echo (true and false); // false  (cú pháp PHP)
echo (true or false);  // true

// ── Toán tử 3 ngôi ───────────────────────────
$diem = 7;
$ket_qua = ($diem >= 5) ? "Đậu" : "Trượt";
echo $ket_qua; // "Đậu"

// ── Null coalescing ──────────────────────────
$ten = null;
$hien_thi = $ten ?? "Khách"; // nếu null thì dùng "Khách"
echo $hien_thi; // "Khách"
✅ Đánh dấu đã học xong

Bài 7 Xử lý chuỗi

strings.gp
<gp>
$s = "  Hello, GracePL World!  ";

// Độ dài và cắt
echo strlen($s);         // 26
echo trim($s);           // "Hello, GracePL World!"
echo ltrim($s);          // trim trái
echo rtrim($s);          // trim phải

// Chuyển hoa/thường
echo strtoupper($s);     // "  HELLO, GRACEPL WORLD!  "
echo strtolower($s);     // "  hello, gracepl world!  "

// Tìm kiếm
$pos = strpos($s, "GracePL"); // vị trí đầu tiên (hoặc false)
if ($pos !== false) {
    echo "Tìm thấy tại vị trí $pos";
}
echo str_contains($s, "World");     // true
echo str_starts_with($s, "  Hello"); // true
echo str_ends_with($s, "World!  "); // true

// Thay thế & cắt
echo str_replace("GracePL", "GP", $s); // thay "GracePL" bằng "GP"
echo substr($s, 9, 7);  // "GracePL" (bắt đầu tại 9, lấy 7 ký tự)
echo substr($s, -8);    // "World!  " (từ cuối)

// Tách & ghép
$parts = explode(", ", "Alice,Bob,Charlie", 3); // tách theo dấu ","
foreach ($parts as $name) { echo $name . "\n"; }

$bienso = ["Hà Nội", "HCM", "Đà Nẵng"];
echo implode(" | ", $bienso); // "Hà Nội | HCM | Đà Nẵng"

// Escape HTML / xóa thẻ / Markdown / slug
echo htmlspecialchars("<script>");  // in ra &lt;script&gt;
echo htmlentities($s); echo html_decode("&lt;"); // encode/decode HTML
echo addslashes("O'Brien");        // O\'Brien
echo strip_tags("<b>Hello</b>");   // Hello
echo markdown_to_html("**bold**"); // <strong>bold</strong>
echo slugify("Xin chào GracePL!", "vi"); // xin-chao-gracepl

// Định dạng
$ten = "Alice"; $diem = 9.5;
echo sprintf("%-10s: %.1f điểm", $ten, $diem); // "Alice     : 9.5 điểm"

// Regex
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', "2026-03-07")) {
    echo "Định dạng ngày hợp lệ!";
}
$email_cleaned = preg_replace('/\s+/', '', "user @example. com");
echo $email_cleaned; // "user@example.com"
✅ Đánh dấu đã học xong

Bài 8 Điều kiện if / else / match

conditions.gp
<gp>
$diem = 75;

// ── if / elseif / else ──────────────────────
if ($diem >= 90) {
    echo "Xuất sắc";
} elseif ($diem >= 80) {
    echo "Giỏi";
} elseif ($diem >= 65) {
    echo "Khá";
} elseif ($diem >= 50) {
    echo "Trung bình";
} else {
    echo "Yếu — cần ôn thêm!";
}

// ── switch ──────────────────────────────────
$ngay = "Monday";
switch ($ngay) {
    case "Monday":
    case "Tuesday":
        echo "Đầu tuần";
        break;
    case "Wednesday":
        echo "Giữa tuần";
        break;
    case "Saturday":
    case "Sunday":
        echo "Cuối tuần 🎉";
        break;
    default:
        echo "Ngày bình thường";
}

// ── match expression (như switch nhưng gọn hơn) ──
$status_code = 404;
$message = match($status_code) {
    200, 201 => "Thành công",
    400      => "Yêu cầu không hợp lệ",
    401, 403 => "Không có quyền",
    404      => "Không tìm thấy",
    500      => "Lỗi máy chủ",
    default  => "Lỗi không xác định ($status_code)"
};
echo $message; // "Không tìm thấy"
✅ Đánh dấu đã học xong

Bài 9 Vòng lặp

loops.gp
<gp>
// ── for ────────────────────────────────────
for ($i = 1; $i <= 5; $i++) {
    echo "Lần $i\n";
}

// ── while ───────────────────────────────────
$n = 10;
while ($n > 0) {
    echo $n . " ";
    $n -= 3;
}
echo "\n"; // 10 7 4 1

// ── do...while — chạy ít nhất 1 lần ──────────
$count = 0;
do {
    echo "Thực hiện lần " . ($count + 1) . "\n";
    $count++;
} while ($count < 3);

// ── foreach — duyệt mảng ────────────────────
$fruits = ["Táo", "Cam", "Chuối", "Xoài"];
foreach ($fruits as $index => $fruit) {
    echo "$index: $fruit\n";
}

// ── foreach với map ─────────────────────────
$scores = ["Alice" => 95, "Bob" => 80, "Charlie" => 75];
foreach ($scores as $name => $score) {
    echo "$name: $score điểm\n";
}

// ── break & continue ───────────────────────
for ($i = 1; $i <= 10; $i++) {
    if ($i == 5) continue; // bỏ qua 5
    if ($i == 8) break;    // dừng tại 8
    echo $i . " ";         // 1 2 3 4 6 7
}
✅ Đánh dấu đã học xong

Bài 10 Hàm (Function)

functions.gp
<gp>
// ── Hàm cơ bản ──────────────────────────────
function chao($ten) {
    return "Xin chào, $ten!";
}
echo chao("Alice"); // "Xin chào, Alice!"

// ── Giá trị mặc định ─────────────────────────
function tinhDien($kwh, $gia_don = 1800) {
    return $kwh * $gia_don;
}
echo tinhDien(100);       // 180000
echo tinhDien(100, 2000); // 200000

// ── Nhiều tham số, toán tử splat ─────────────
function tong(...$nums) {
    $s = 0;
    foreach ($nums as $n) $s += $n;
    return $s;
}
echo tong(1, 2, 3, 4, 5); // 15

// ── Closure (hàm ẩn danh) ──────────────────
$nhan_doi = function($x) { return $x * 2; };
echo $nhan_doi(7); // 14

// ── Higher-order functions ───────────────────
$nums = [1, 2, 3, 4, 5, 6];

$chan  = array_filter($nums, function($n) { return $n % 2 == 0; });
$binh_phuong = array_map(function($n) { return $n ** 2; }, $nums);
$tong_tat_ca = array_reduce($nums, function($carry, $n) { return $carry + $n; }, 0);

print_r($chan);        // [2, 4, 6]
print_r($binh_phuong); // [1, 4, 9, 16, 25, 36]
echo $tong_tat_ca;    // 21

// ── Trả về nhiều giá trị ─────────────────────
function chia_for($a, $b) {
    if ($b == 0) return [null, "Không thể chia cho 0"];
    return [$a / $b, null];
}
[$ket_qua, $loi] = chia_for(10, 3);
if ($loi !== null) { echo "Lỗi: $loi"; }
else { echo "Kết quả: $ket_qua"; }
✅ Đánh dấu đã học xong

Bài 11 Mảng (Array) & Map

arrays.gp
<gp>
// ── Mảng số (indexed) ───────────────────────
$fruits = ["Táo", "Cam", "Xoài"];
echo $fruits[0];       // "Táo"
echo count($fruits);   // 3
$fruits[] = "Chuối";   // thêm vào cuối
array_push($fruits, "Dâu"); // tương tự

echo array_pop($fruits);    // "Dâu" (xóa & trả về cuối)
echo array_shift($fruits);  // "Táo" (xóa & trả về đầu)
array_unshift($fruits, "Nho"); // thêm vào đầu

// ── Sắp xếp ─────────────────────────────────
$nums = [3, 1, 4, 1, 5, 9, 2, 6];
sort($nums);  // sắp xếp tăng dần
rsort($nums); // sắp xếp giảm dần
echo implode(", ", $nums);

// ── Tìm kiếm ────────────────────────────────
echo in_array("Cam", $fruits);     // true
echo array_search("Cam", $fruits); // index của "Cam"

// ── Cắt & nối ──────────────────────────────
$a = [1, 2, 3]; $b = [4, 5, 6];
$c = array_merge($a, $b); // [1,2,3,4,5,6]
$slice = array_slice($c, 1, 3); // [2,3,4]

// ── Mảng kết hợp (Map / Associative Array) ──
$user = [
    "ten"   => "Alice",
    "tuoi"  => 25,
    "email" => "alice@example.com"
];
echo $user["ten"];   // "Alice"
$user["city"] = "Hà Nội"; // thêm key mới

// Duyệt
foreach ($user as $key => $val) {
    echo "$key: $val\n";
}

// Kiểm tra key tồn tại
if (array_key_exists("email", $user)) {
    echo "Email: " . $user["email"];
}
echo isset($user["phone"]); // false

// ── array_keys & array_values ──────────────
$keys = array_keys($user);   // ["ten","tuoi","email","city"]
$vals = array_values($user); // ["Alice",25,"alice@example.com","Hà Nội"]
⚠️ array_push trả về số, không phải mảng: array_push($arr, $val) sửa $arr trực tiếp và trả về số phần tử mới. Không viết $arr = array_push($arr, $val) — câu đó sẽ ghi đè $arr bằng một số nguyên.
✅ Đánh dấu đã học xong

Bài 12 JSON — Trao đổi dữ liệu

json.gp
<gp>
// ── Encode: GracePL → JSON string ───────────
$data = [
    "ten"    => "Alice",
    "tuoi"   => 25,
    "skills" => ["Go", "GracePL", "Docker"],
    "active" => true
];

$json = json_encode($data);
echo $json;
// {"ten":"Alice","tuoi":25,"skills":["Go","GracePL","Docker"],"active":true}

// JSON đẹp (pretty-print)
$pretty = json_encode($data, JSON_PRETTY_PRINT);
echo $pretty;

// ── Decode: JSON string → GracePL ───────────
$json_str = '{"name":"Bob","age":30,"scores":[85,92,78]}';
$obj = json_decode($json_str);
echo $obj["name"];          // "Bob"
echo $obj["scores"][1];     // 92

// ── Đọc/ghi file JSON ───────────────────────
// Ghi
file_put_contents("config.json", json_encode($data, JSON_PRETTY_PRINT));

// Đọc
$config = json_decode(file_get_contents("config.json"));
echo $config["ten"]; // "Alice"

// ── Decode array of objects ──────────────────
$list_json = '[{"id":1,"name":"Apple"},{"id":2,"name":"Banana"}]';
$items = json_decode($list_json);
foreach ($items as $item) {
    echo $item["id"] . ": " . $item["name"] . "\n";
}
✅ Đánh dấu đã học xong

Bài 13 Class & OOP (Lập trình hướng đối tượng)

oop.gp
<gp>
class User {
    // Thuộc tính (properties)
    public $ten;
    public $email;
    private $mat_khau; // chỉ truy cập trong class
    public static $so_nguoi_dung = 0; // class variable

    // Constructor — gọi khi tạo đối tượng
    public function __construct($ten, $email, $mat_khau) {
        $this->ten = $ten;
        $this->email = $email;
        $this->mat_khau = password_hash($mat_khau);
        self::$so_nguoi_dung++;
    }

    // Phương thức thông thường
    public function xin_chao() {
        return "Xin chào, tôi là {$this->ten}!";
    }

    // Getter (truy cập biến private)
    public function get_email() {
        return $this->email;
    }

    // Verify password
    public function kiem_tra_mat_khau($mat_khau_nhap) {
        return password_verify($mat_khau_nhap, $this->mat_khau);
    }

    // Static method — gọi qua tên class không cần tạo đối tượng
    public static function dem_nguoi_dung() {
        return self::$so_nguoi_dung;
    }

    // __toString — hiển thị đối tượng dưới dạng chuỗi
    public function __toString() {
        return "User({$this->ten}, {$this->email})";
    }
}

// Tạo đối tượng
$alice = new User("Alice", "alice@example.com", "password123");
$bob   = new User("Bob",   "bob@example.com",   "secret456");

echo $alice->xin_chao();                    // "Xin chào, tôi là Alice!"
echo $alice->kiem_tra_mat_khau("password123"); // true
echo User::dem_nguoi_dung();               // 2
echo $alice;                               // "User(Alice, alice@example.com)"
✅ Đánh dấu đã học xong

Bài 14 Kế thừa & Interface

inheritance.gp
<gp>
// ── Class cha ───────────────────────────────
class Animal {
    public $ten;
    public function __construct($ten) { $this->ten = $ten; }
    public function mo_ta() { return "Tôi là {$this->ten}"; }
    public function keu() { return "..."; }
}

// ── Kế thừa với extends ──────────────────────
class Dog extends Animal {
    public $giong;
    public function __construct($ten, $giong) {
        parent::__construct($ten); // gọi constructor cha
        $this->giong = $giong;
    }
    public function keu() { return "Gâu! Gâu!"; } // override
    public function mo_ta() {
        return parent::mo_ta() . " ({$this->giong})"; // mở rộng
    }
}

class Cat extends Animal {
    public function keu() { return "Meo! Meo!"; }
}

$dog = new Dog("Buddy", "Golden Retriever");
$cat = new Cat("Mimi");

echo $dog->mo_ta();  // "Tôi là Buddy (Golden Retriever)"
echo $dog->keu();    // "Gâu! Gâu!"
echo $cat->keu();    // "Meo! Meo!"

// Polymorphism — xử lý các loại khác nhau qua interface chung
$animals = [$dog, $cat, new Dog("Rex", "German Shepherd")];
foreach ($animals as $a) {
    echo $a->ten . ": " . $a->keu() . "\n";
}
✅ Đánh dấu đã học xong

Bài 15 Interface & Trait

interface.gp
<gp>
// Interface: định nghĩa "hợp đồng" — class phải implement
interface Serializable {
    public function serialize(): string;
    public function to_array(): array;
}

interface Loggable {
    public function log_info(): string;
}

// Implement nhiều interface với implements
class UserProfile implements Serializable, Loggable {
    public $id;
    public $ten;
    public $email;

    public function __construct($id, $ten, $email) {
        $this->id = $id; $this->ten = $ten; $this->email = $email;
    }

    public function serialize(): string {
        return json_encode($this->to_array());
    }
    public function to_array(): array {
        return ["id" => $this->id, "ten" => $this->ten, "email" => $this->email];
    }
    public function log_info(): string {
        return "[User#{$this->id}] {$this->ten} <{$this->email}>";
    }
}

$profile = new UserProfile(1, "Alice", "alice@example.com");
echo $profile->serialize();  // JSON string
echo $profile->log_info();   // "[User#1] Alice "

// Trait: tái sử dụng code không qua kế thừa
trait Timestamps {
    public $created_at;
    public $updated_at;

    public function touch() {
        $this->updated_at = date("Y-m-d H:i:s");
    }
    public function just_created() {
        $this->created_at = date("Y-m-d H:i:s");
        $this->updated_at = $this->created_at;
    }
}

class Post {
    use Timestamps;
    public $title;
    public function __construct($title) {
        $this->title = $title;
        $this->just_created();
    }
}

$post = new Post("Bài viết đầu tiên");
echo $post->created_at; // ngày tạo
✅ Đánh dấu đã học xong

Bài 16 HTTP Server đầu tiên

🎯 Mục tiêu: Tạo web server chạy được, xử lý requests và trả về responses.

Server đơn giản nhất

server.gp
<gp>
// Đăng ký route "/" — xử lý mọi request tới trang chủ
http_handle("/", function($req) {
    // $req chứa: method, path, headers, query, body, params, cookies
    $method = $req["method"];  // "GET", "POST", ...
    $path   = $req["path"];    // "/", "/about", ...

    // Trả về chuỗi HTML
    return "<h1>Xin chào từ GracePL! 🚀</h1><p>Method: $method</p>";
});

http_handle("/about", function($req) {
    return "<h1>Về chúng tôi</h1><p>GracePL v3.0</p>";
});

// Khởi động server trên port 8080
echo "Server đang chạy tại http://localhost:8080\n";
http_serve(":8080");
terminal
gracepl run server.gp
# Mở trình duyệt: http://localhost:8080

Cấu trúc đối tượng $req

gracepl
<gp>
http_handle("/debug", function($req) {
    // Xem toàn bộ request
    $method  = $req["method"];           // "GET"
    $path    = $req["path"];             // "/debug"
    $query   = $req["query"];            // ["key" => "value"] từ ?key=value
    $headers = $req["headers"];          // map headers
    $body    = $req["body"];             // request body (string). Form/multipart → $req["files"][fieldName] = [{name, size, temp_path, content_type}]
    $ip      = $req["remote_addr"];      // "127.0.0.1:56789"
    $cookies = $req["cookies"];          // map cookies
    // Redirect: http_redirect($url); http_redirect_back([$fallback]) — về Referer hoặc fallback (mặc định /)
    // Session flash (one-time message): session_flash("key", "value"); $msg = session_flash_get("key");

    // Query string: GET /debug?name=Alice&page=2
    $name = $query["name"] ?? "Khách";
    $page = intval($query["page"] ?? 1);

    return "<p>Xin chào $name, trang $page</p>";
});
http_serve(":8080");
✅ Đánh dấu đã học xong

Bài 17 Route Parameters & Định tuyến nâng cao

routing.gp
<gp>
// ── Route parameters (:id, :slug...) ───────
http_handle("/users/:id", function($req) {
    $id = $req["params"]["id"];     // lấy từ URL
    return "<p>User ID: $id</p>";
});

http_handle("/posts/:year/:slug", function($req) {
    $year = $req["params"]["year"];
    $slug = $req["params"]["slug"];
    return "<p>Bài viết $slug năm $year</p>";
});

// ── Method-specific routes ───────────────────
http_route_get("/api/users", function($req) {
    return json_encode(["users" => []]); // danh sách
});

http_route_post("/api/users", function($req) {
    $body = json_decode($req["body"]);
    // Tạo user mới
    return json_encode(["created" => true, "name" => $body["name"]]);
});

http_route_put("/api/users/:id", function($req) {
    $id = $req["params"]["id"];
    $body = json_decode($req["body"]);
    return json_encode(["updated" => $id]);
});

http_route_delete("/api/users/:id", function($req) {
    $id = $req["params"]["id"];
    return json_encode(["deleted" => $id]);
});

// ── Static files ─────────────────────────────
http_static("/static/", "public/"); // phục vụ file từ thư mục public/

// ── Middleware ───────────────────────────────
http_request_id();   // mỗi request có ID duy nhất
http_gzip();         // nén response

echo "API server: http://localhost:8080\n";
http_serve(":8080");
⚠️ Route wildcards không hoạt động: /* là path literal, không phải wildcard. Chỉ dùng :param hoặc {param} cho segment động.
/news/:slug ✅ — /news/*
⚠️ Kiểm tra key query string: $req["query"]["x"] != false không đáng tin cậy vì null != false trả về true trong GracePL. Dùng isset($req["query"]["x"]).
✅ Đánh dấu đã học xong

Bài 18 Xây dựng REST API hoàn chỉnh

api.gp — REST API mẫu
<gp>
// Dữ liệu in-memory (thực tế dùng database)
$todos = [
    ["id" => 1, "title" => "Học GracePL", "done" => false],
    ["id" => 2, "title" => "Xây dựng API",  "done" => true],
];
$next_id = 3;

// GET /api/todos — lấy danh sách
http_route_get("/api/todos", function($req) use (&$todos) {
    http_json($todos);
});

// GET /api/todos/:id — lấy một item
http_route_get("/api/todos/:id", function($req) use (&$todos) {
    $id = intval($req["params"]["id"]);
    foreach ($todos as $todo) {
        if ($todo["id"] == $id) {
            http_json($todo);
            return;
        }
    }
    http_json(["error" => "Không tìm thấy"], 404);
});

// POST /api/todos — tạo mới
http_route_post("/api/todos", function($req) use (&$todos, &$next_id) {
    $body = json_decode($req["body"]);
    if (empty($body["title"])) {
        http_json(["error" => "title là bắt buộc"], 400);
        return;
    }
    $new_todo = ["id" => $next_id++, "title" => $body["title"], "done" => false];
    $todos[] = $new_todo;
    http_json($new_todo, 201);
});

// PUT /api/todos/:id — cập nhật
http_route_put("/api/todos/:id", function($req) use (&$todos) {
    $id   = intval($req["params"]["id"]);
    $body = json_decode($req["body"]);
    foreach ($todos as &$todo) {
        if ($todo["id"] == $id) {
            if (isset($body["title"])) $todo["title"] = $body["title"];
            if (isset($body["done"]))  $todo["done"]  = (bool)$body["done"];
            http_json($todo);
            return;
        }
    }
    http_json(["error" => "Không tìm thấy"], 404);
});

// DELETE /api/todos/:id
http_route_delete("/api/todos/:id", function($req) use (&$todos) {
    $id = intval($req["params"]["id"]);
    $todos = array_filter($todos, function($t) use ($id) { return $t["id"] != $id; });
    $todos = array_values($todos);
    http_json(["deleted" => $id]);
});

http_cors();    // bật CORS cho frontend
echo "Todo API: http://localhost:8080\n";
http_serve(":8080");

Test bằng curl

bash
# Lấy danh sách
curl http://localhost:8080/api/todos

# Tạo mới
curl -X POST http://localhost:8080/api/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"Học REST API"}'

# Cập nhật
curl -X PUT http://localhost:8080/api/todos/1 \
  -H "Content-Type: application/json" \
  -d '{"done":true}'

# Xóa
curl -X DELETE http://localhost:8080/api/todos/1
✅ Đánh dấu đã học xong

Bài 19 Render HTML Template

templates/index.html
<!DOCTYPE html>
<html lang="vi">
<head><title>{{page_title}} — MyApp</title></head>
<body>
  <h1>Xin chào, {{username}}!</h1>

  {{if is_admin}}
  <div class="admin-banner">Bạn đang đăng nhập với quyền Admin</div>
  {{endif}}
  <!-- {{if}} hỗ trợ lồng nhau. Filter: {{key | slice:start:end}}, {{key | upper}}/lower, {{key | default:"x"}}, {{key | length}} -->

  <h2>Danh sách bài viết</h2>
  {{if posts}}
  <ul>
    {{foreach posts as post}}
    <li>
      <a href="/posts/{{post.id}}">{{post.title}}</a>
      <span>{{post.date}}</span>
    </li>
    {{end}}
  </ul>
  {{else}}
  <p>Chưa có bài viết nào.</p>
  {{endif}}

  {{include "templates/footer.html"}}
</body>
</html>
server.gp
<gp>
http_handle("/", function($req) {
    $posts = [
        ["id" => 1, "title" => "Giới thiệu GracePL", "date" => "2026-01-15"],
        ["id" => 2, "title" => "Xây dựng REST API",   "date" => "2026-02-01"],
        ["id" => 3, "title" => "Deploy lên production","date" => "2026-03-07"],
    ];

    $html = render("templates/index.html", [
        "page_title" => "Trang chủ",
        "username"   => "Alice",
        "is_admin"   => true,
        "posts"      => $posts,
    ]);

    // Trả về HTML với đúng Content-Type
    http_set_header("Content-Type", "text/html; charset=utf-8");
    return $html;
});

http_serve(":8080");
💡 Bảo mật XSS: Biến {{ten}} tự động escape HTML — < thành &lt;. Nếu cần xuất HTML thô (trusted), dùng {{ten | raw}}.
⚠️ Biến chứa HTML markup phải dùng | raw: Khi bạn render nhiều card/item thành chuỗi HTML rồi truyền vào template cha, biến đó phải dùng | raw:
{{cards}} → hiển thị text thô &lt;div&gt;...&lt;/div&gt;
{{cards | raw}} → render HTML đúng cách ✅
✅ Đánh dấu đã học xong

Bài 20 CRUD với Database

🎯 Mục tiêu: Kết nối database và thực hiện Create, Read, Update, Delete.
db.gp
<gp>
// ── Kết nối ──────────────────────────────────
// SQLite (không cần cài server — tốt cho học)
$db = db_connect("sqlite://./data.db");

// MySQL / MariaDB
// $db = db_connect("mysql://user:pass@localhost:3306/mydb");

// PostgreSQL
// $db = db_connect("postgres://user:pass@localhost:5432/mydb");

// ── Tạo bảng ────────────────────────────────
db_exec($db, "CREATE TABLE IF NOT EXISTS users (
    id      INTEGER PRIMARY KEY AUTOINCREMENT,
    ten     TEXT    NOT NULL,
    email   TEXT    NOT NULL UNIQUE,
    tuoi    INTEGER,
    ngay_tao TEXT DEFAULT CURRENT_TIMESTAMP
)");

// ── CREATE — thêm dữ liệu ───────────────────
db_exec("INSERT INTO users (ten, email, tuoi) VALUES (?, ?, ?)",
    ["Alice", "alice@example.com", 25]);

// ── READ — đọc dữ liệu ──────────────────────
$users = db_query("SELECT * FROM users ORDER BY id");
foreach ($users as $u) {
    echo "{$u['id']}: {$u['ten']} ({$u['email']})\n";
}

// Một bản ghi
$user = db_query_row("SELECT * FROM users WHERE id = ?", [1]);
if ($user) echo "Tìm thấy: " . $user["ten"];

// Với điều kiện
$adults = db_query("SELECT * FROM users WHERE tuoi >= ?", [18]);

// ── UPDATE — cập nhật ───────────────────────
db_exec("UPDATE users SET tuoi = ?, email = ? WHERE id = ?",
    [26, "alice_new@example.com", 1]);

// ── DELETE — xóa ────────────────────────────
db_exec("DELETE FROM users WHERE id = ?", [1]);

// ── Transaction — đảm bảo tính toàn vẹn ─────
$tx = db_begin();
try {
    db_tx_exec($tx, "INSERT INTO users (ten, email, tuoi) VALUES (?, ?, ?)", ["Bob", "bob@example.com", 30]);
    db_tx_exec($tx, "INSERT INTO users (ten, email, tuoi) VALUES (?, ?, ?)", ["Carol", "carol@example.com", 28]);
    db_commit($tx);
    echo "Transaction thành công!";
} catch ($e) {
    db_rollback($tx);
    echo "Transaction thất bại: " . $e->getMessage();
}

db_close();
✅ Đánh dấu đã học xong

Bài 21 Form Validation

validation.gp
<gp>
http_handle("/register", function($req) {
    if ($req["method"] == "GET") {
        // Hiện form đăng ký
        return render("templates/register.html", ["errors" => []]);
    }

    // POST — xử lý form
    $body = json_decode($req["body"]);

    // Validate với rule engine tích hợp
    $result = validate($body, [
        "ten"       => "required|string|min:2|max:50",
        "email"     => "required|email",
        "mat_khau"  => "required|string|min:8",
        "tuoi"      => "required|numeric|min:13|max:120",
        "vai_tro"   => "required|in:user,admin"
    ]);

    if (!$result["valid"]) {
        http_json(["errors" => $result["errors"]], 422);
        return;
    }

    // Dữ liệu hợp lệ — lưu vào DB
    db_connect("sqlite://./app.db");
    db_exec("INSERT INTO users (ten, email, mat_khau, tuoi) VALUES (?, ?, ?, ?)",
        [$body["ten"], $body["email"], password_hash($body["mat_khau"]), intval($body["tuoi"])]);
    db_close();

    http_json(["success" => true, "user_id" => $id], 201);
});

http_serve(":8080");
✅ Đánh dấu đã học xong

Bài 22 Xác thực với JWT

auth.gp
<gp>
env_load(".env");
$JWT_SECRET = env("JWT_SECRET", "change-this-secret-in-production");
$db = db_connect(env("DB_URL", "sqlite://./app.db"));

// ── Login ─────────────────────────────────────
http_route_post("/auth/login", function($req) use ($db, $JWT_SECRET) {
    $body = json_decode($req["body"]);
    $email = $body["email"] ?? "";
    $pass  = $body["password"] ?? "";

    if (empty($email) || empty($pass)) {
        http_json(["error" => "Thiếu email hoặc mật khẩu"], 400);
        return;
    }

    $user = db_query_row($db, "SELECT * FROM users WHERE email = ?", [$email]);
    if (!$user || !password_verify($pass, $user["mat_khau"])) {
        http_json(["error" => "Email hoặc mật khẩu không đúng"], 401);
        return;
    }

    // Tạo JWT token — hết hạn sau 24 giờ
    $token = jwt_encode([
        "user_id" => $user["id"],
        "email"   => $user["email"],
        "role"    => $user["vai_tro"] ?? "user"
    ], $JWT_SECRET, 86400);

    http_json(["token" => $token, "user" => ["id" => $user["id"], "ten" => $user["ten"]]]);
});

// ── Middleware xác thực ──────────────────────
function require_auth($req, $secret) {
    $auth = $req["headers"]["Authorization"] ?? $req["headers"]["authorization"] ?? "";
    if (!str_starts_with($auth, "Bearer ")) {
        http_json(["error" => "Cần xác thực"], 401);
        return null;
    }
    $token = substr($auth, 7);
    $claims = jwt_decode($token, $secret);
    if (!$claims) {
        http_json(["error" => "Token không hợp lệ hoặc đã hết hạn"], 401);
        return null;
    }
    return $claims; // trả về payload của token
}

// ── Protected route ──────────────────────────
http_route_get("/api/profile", function($req) use ($db, $JWT_SECRET) {
    $claims = require_auth($req, $JWT_SECRET);
    if (!$claims) return; // đã trả error trong require_auth

    $user = db_query_row($db, "SELECT id,ten,email FROM users WHERE id = ?", [$claims["user_id"]]);
    http_json($user);
});

echo "Auth server: http://localhost:8080\n";
http_serve(":8080");
✅ Đánh dấu đã học xong

Bài 23 Xử lý File & Upload

files.gp
<gp>
// ── Đọc / ghi file ──────────────────────────
$content = file_get_contents("data.txt");
file_put_contents("output.txt", "Hello from GracePL!\n");
file_put_contents("log.txt", date("Y-m-d H:i:s") . " - Event\n", FILE_APPEND);

// ── Kiểm tra file / thư mục ─────────────────
if (file_exists("config.json")) {
    $config = json_decode(file_get_contents("config.json"));
}
echo is_file("script.gp");   // true
echo is_dir("templates/");   // true

// ── Làm việc với thư mục ────────────────────
mkdir("uploads/avatars/", 0755, true); // tạo đệ quy
$files = scandir("uploads/");          // liệt kê file
$phpFiles = glob("*.gp");             // wildcard
$subDirs = glob("src/*/", GLOB_ONLYDIR); // chỉ thư mục

// ── Upload file qua HTTP (multipart form) ────
// $req["files"][fieldName] = array of {name, size, temp_path, content_type}
http_route_post("/upload", function($req) {
    $files = $req["files"]["file"] ?? [];  // mảng file (có thể nhiều file 1 field)
    $file = $files[0] ?? null;

    if (!$file) {
        http_json(["error" => "Không tìm thấy file"], 400);
        return;
    }

    // Kiểm tra mime type (bảo mật)
    $allowed = ["image/jpeg", "image/png", "image/gif", "application/pdf"];
    if (!in_array($file["content_type"], $allowed)) {
        http_json(["error" => "Loại file không được phép"], 415);
        return;
    }

    // Kiểm tra kích thước (5MB max)
    if ($file["size"] > 5 * 1024 * 1024) {
        http_json(["error" => "File quá lớn (max 5MB)"], 413);
        return;
    }

    // Tạo tên file ngẫu nhiên để tránh overwrite
    $ext = substr($file["name"], strrpos($file["name"], "."));
    $newname = "uploads/" . sha1($file["name"] . time()) . $ext;

    // Lưu file (temp_path là đường dẫn file tạm)
    file_write_bytes($newname, file_read_bytes($file["temp_path"]));

    http_json(["url" => "/static/" . basename($newname), "size" => $file["size"]]);
});

http_static("/static/", "uploads/");
http_serve(":8080");
✅ Đánh dấu đã học xong

Bài 24 Concurrency — Goroutine & Channel

🎯 Mục tiêu: Chạy nhiều tác vụ song song — GracePL dùng model goroutine và channel giống Go.
concurrency.gp
<gp>
// ── go {} — chạy trong goroutine mới ─────────
$ch = chan_make(10); // channel với buffer 10

go {
    sleep_ms(100); // giả lập xử lý
    chan_send($ch, "Kết quả từ goroutine 1");
}

go {
    sleep_ms(200);
    chan_send($ch, "Kết quả từ goroutine 2");
}

// Nhận kết quả từ 2 goroutines
$r1 = chan_recv($ch);
$r2 = chan_recv($ch);
echo "$r1\n$r2\n";

// ── WaitGroup — chờ nhiều goroutines ─────────
$wg = wg_make();
$results = [];

for ($i = 1; $i <= 5; $i++) {
    wg_add($wg, 1);
    $n = $i;
    go_run(function() use ($wg, $n, &$results) {
        sleep_ms($n * 50);
        $results[] = "Task $n xong";
        wg_done($wg);
    });
}

wg_wait($wg); // chờ tất cả xong
foreach ($results as $r) echo $r . "\n";

// ── Parallel HTTP requests ────────────────────
$urls = [
    "https://jsonplaceholder.typicode.com/users/1",
    "https://jsonplaceholder.typicode.com/users/2",
    "https://jsonplaceholder.typicode.com/users/3",
];

$chan = chan_make(count($urls));

foreach ($urls as $url) {
    go_run(function() use ($url, $chan) {
        $data = json_decode(http_get($url));
        chan_send($chan, $data["name"] ?? "unknown");
    });
}

for ($i = 0; $i < count($urls); $i++) {
    echo chan_recv($chan) . "\n";
}
✅ Đánh dấu đã học xong

Bài 25 Xử lý lỗi & Exception

errors.gp
<gp>
// ── try / catch / finally ────────────────────
function chia($a, $b) {
    if ($b == 0) {
        throw new Exception("Không thể chia cho 0");
    }
    return $a / $b;
}

try {
    $result = chia(10, 0);
    echo $result;
} catch (Exception $e) {
    echo "Lỗi: " . $e->message . "\n";
    echo "Tại: " . $e->file . ":" . $e->line . "\n";
} finally {
    echo "Khối finally luôn chạy\n";
}

// ── Custom Exception ─────────────────────────
class ValidationException extends Exception {
    public $errors;
    public function __construct($errors) {
        parent::__construct("Dữ liệu không hợp lệ");
        $this->errors = $errors;
    }
}

function createUser($data) {
    $errors = [];
    if (empty($data["ten"])) $errors[] = "Thiếu tên";
    if (empty($data["email"])) $errors[] = "Thiếu email";
    if (!empty($errors)) throw new ValidationException($errors);
    // ...tạo user
}

try {
    createUser(["ten" => ""]);
} catch (ValidationException $e) {
    echo "Lỗi validation:\n";
    foreach ($e->errors as $err) echo "  - $err\n";
} catch (Exception $e) {
    echo "Lỗi chung: " . $e->message;
}

// ── is_error() — kiểm tra giá trị lỗi ────────
$result = db_connect("invalid://bad-url");
if (is_error($result)) {
    echo "Kết nối thất bại!";
    log_error("DB connection failed", ["url" => "invalid://bad-url"]);
}
✅ Đánh dấu đã học xong

Bài 26 🏗️ Dự án thực tế: Blog API + Frontend

🎯 Mục tiêu: Tổng hợp tất cả kiến thức để xây dựng một ứng dụng blog hoàn chỉnh từ đầu đến cuối.

Cấu trúc dự án

cây thư mục
my-blog/
├── server.gp          # Entry point
├── .env               # Cấu hình (DB, JWT_SECRET...)
├── db/
│   └── migrations.gp  # Tạo bảng
├── templates/
│   ├── base.html      # Layout chung (header/footer)
│   ├── home.html      # Trang chủ
│   ├── post.html      # Chi tiết bài viết
│   └── admin.html     # Quản lý (cần đăng nhập)
└── public/
    ├── style.css
    └── app.js

server.gp — Entry point

server.gp
<gp>
env_load(".env");
$DB  = db_connect(env("DB_URL", "sqlite://./blog.db"));
$KEY = env("JWT_SECRET", "my-secret-key");
$PORT = env("PORT", "8080");

// ── Init DB ──────────────────────────────────
db_exec($DB, "CREATE TABLE IF NOT EXISTS posts (
    id        INTEGER PRIMARY KEY AUTOINCREMENT,
    title     TEXT NOT NULL,
    slug      TEXT UNIQUE,
    content   TEXT,
    author    TEXT,
    published INTEGER DEFAULT 0,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
)");

// ── Middleware ────────────────────────────────
http_request_id();
http_panic_recover();
http_security_headers();
http_gzip();
log_format("json");
http_static("/static/", "public/");

// ── Trang chủ (công khai) ────────────────────
http_route_get("/", function($req) use ($DB) {
    $posts = db_query($DB, "SELECT id,title,slug,author,created_at FROM posts WHERE published=1 ORDER BY id DESC LIMIT 10");
    return render("templates/home.html", ["posts" => $posts, "title" => "Blog GracePL"]);
});

// ── Chi tiết bài viết ────────────────────────
http_route_get("/posts/:slug", function($req) use ($DB) {
    $slug = $req["params"]["slug"];
    $post = db_query_row($DB, "SELECT * FROM posts WHERE slug=? AND published=1", [$slug]);
    if (!$post) { http_status(404); return render("templates/404.html", []); }
    return render("templates/post.html", ["post" => $post]);
});

// ── API — cần xác thực ───────────────────────
http_route_post("/api/posts", function($req) use ($DB, $KEY) {
    $auth = $req["headers"]["Authorization"] ?? "";
    $claims = jwt_decode(substr($auth, 7), $KEY);
    if (!$claims || $claims["role"] !== "admin") {
        http_json(["error" => "Không có quyền"], 403); return;
    }
    $b = json_decode($req["body"]);
    $v = validate($b, ["title"=>"required|min:3", "content"=>"required|min:10"]);
    if (!$v["valid"]) { http_json(["errors"=>$v["errors"]], 422); return; }
    $slug = strtolower(str_replace(" ", "-", preg_replace('/[^\w\s-]/', '', $b["title"])));
    db_exec("INSERT INTO posts (title,slug,content,author,published) VALUES (?,?,?,?,?)", [$b["title"],$slug,$b["content"],$claims["name"],1]);
    http_json(["id"=>$id, "slug"=>$slug], 201);
});

// ── Auth ──────────────────────────────────────
http_route_post("/api/login", function($req) use ($KEY) {
    $b = json_decode($req["body"]);
    // Trong thực tế: kiểm tra DB
    if ($b["email"] === env("ADMIN_EMAIL","admin@blog.com") && 
        $b["password"] === env("ADMIN_PASS","admin123")) {
        $token = jwt_encode(["name"=>"Admin","role"=>"admin"], $KEY, 86400);
        http_json(["token" => $token]);
    } else {
        http_json(["error" => "Sai thông tin đăng nhập"], 401);
    }
});

echo "Blog đang chạy: http://localhost:$PORT\n";
http_serve(":$PORT");

Chạy dự án

bash
cd my-blog
gracepl run server.gp

# Test tạo bài viết
TOKEN=$(curl -s -X POST http://localhost:8080/api/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@blog.com","password":"admin123"}' | grep -o '"token":"[^"]*"' | cut -d'"' -f4)

curl -X POST http://localhost:8080/api/posts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Bài viết đầu tiên","content":"Nội dung bài viết..."}'

# Xem trang chủ
open http://localhost:8080
🎉 Chúc mừng! Bạn đã hoàn thành toàn bộ tutorial GracePL! Bây giờ bạn có thể xây dựng các ứng dụng web thực tế. Hãy xem tài liệu đầy đủ để khám phá thêm các tính năng nâng cao.
✅ Đánh dấu đã học xong