Featured image of post การสร้าง Full Stack Web Application: Vue.js + Tailwind CSS + ASP.NET Core 9 Minimal API ด้วย Modular Monolith Architecture

การสร้าง Full Stack Web Application: Vue.js + Tailwind CSS + ASP.NET Core 9 Minimal API ด้วย Modular Monolith Architecture

เรียนรู้การสร้าง full stack web application ด้วย Vue.js, Tailwind CSS และ ASP.NET Core 9 Minimal API ร่วมกับ Modular Monolith architecture คู่มือฉบับสมบูรณ์สำหรับผู้เริ่มต้นเขียนโปรแกรม

บทนำ: Full Stack Development คืออะไร?

Full Stack Development หมายถึงการพัฒนาทั้งส่วน frontend (ฝั่งผู้ใช้) และ backend (ฝั่งเซิร์ฟเวอร์) ของเว็บแอปพลิเคชัน นักพัฒนา full stack คือผู้ที่สามารถทำงานได้ทั้งส่วนของ user interface ที่ผู้ใช้มองเห็นและโต้ตอบได้ และส่วนของ server logic ที่ทำงานเบื้องหลัง

องค์ประกอบหลักของ Full Stack Development:

  • Frontend (Client-side): สิ่งที่ผู้ใช้เห็นและโต้ตอบ

    • User Interface (UI)
    • User Experience (UX)
    • Client-side logic และการทำงานโต้ตอบ
  • Backend (Server-side): เครื่องยนต์ที่ขับเคลื่อนแอปพลิเคชัน

    • Server logic และ APIs
    • การจัดการฐานข้อมูล
    • Authentication และความปลอดภัย
    • Business logic
  • Database: ที่เก็บและจัดการข้อมูล

  • DevOps: การ deploy และจัดการ infrastructure

ในบทความนี้ เราจะสร้าง full stack application ที่สมบูรณ์โดยใช้เทคโนโลยีสมัยใหม่: Vue.js สำหรับ frontend, Tailwind CSS สำหรับการจัดแต่งหน้าตา และ ASP.NET Core 9 Minimal API สำหรับ backend โดยจัดระบบด้วย Modular Monolith architecture

Vue.js คืออะไร?

Vue.js เป็น progressive JavaScript framework สำหรับสร้าง user interfaces และ single-page applications (SPAs) สร้างโดย Evan You ในปี 2014 Vue.js ได้กลายเป็นหนึ่งใน frontend frameworks ที่ได้รับความนิยมมากที่สุด เคียงข้างกับ React และ Angular

คุณสมบัติหลักของ Vue.js:

  1. Progressive Framework: สามารถใช้ Vue.js แบบค่อยเป็นค่อยไป - ใช้เพียงส่วนหนึ่งของหน้าเว็บหรือสร้างแอปพลิเคชันทั้งหมด
  2. Reactive Data Binding: UI อัปเดตอัตโนมัติเมื่อข้อมูลเปลี่ยน
  3. Component-Based Architecture: สร้าง components ที่ห่อหุ้มและจัดการ state ของตัวเอง
  4. Virtual DOM: การ render ที่มีประสิทธิภาพสำหรับความเร็วที่เหมาะสม
  5. ง่ายต่อการเรียนรู้: syntax ที่เรียบง่าย เข้าใจง่ายสำหรับผู้เริ่มต้น

ข้อดีของ Vue.js:

  • ขนาด bundle เล็ก: โหลดเร็ว
  • เอกสารที่เยี่ยม: ครอบคลุมและเข้าใจง่ายสำหรับผู้เริ่มต้น
  • ระบบนิเวศที่ดี: เครื่องมือและไลบรารีที่หลากหลาย
  • รองรับ TypeScript: การผสานรวม TypeScript อย่างเต็มรูปแบบ
  • Composition API: วิธีสมัยใหม่ในการจัดระเบียบ component logic

ตัวอย่าง Vue.js Component:

<template>
  <div class="user-profile">
    <h2>{{ user.name }}</h2>
    <p>{{ user.email }}</p>
    <button @click="updateProfile">อัปเดตโปรไฟล์</button>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'

const user = reactive({
  name: 'สมชาย ใจดี',
  email: '[email protected]'
})

const updateProfile = () => {
  // อัปเดตโปรไฟล์ผู้ใช้
  console.log('โปรไฟล์ถูกอัปเดตแล้ว!')
}
</script>

อ้างอิง: Vue.js Official Documentation

Tailwind CSS คืออะไร?

Tailwind CSS เป็น utility-first CSS framework ที่ให้ utility classes ระดับต่ำสำหรับสร้างการออกแบบที่กำหนดเองได้โดยตรงใน markup แทนที่จะเขียน custom CSS คุณจะประกอบการออกแบบด้วยการรวม utility classes ขนาดเล็กที่มีจุดประสงค์เดียว

คุณสมบัติหลักของ Tailwind CSS:

  1. Utility-First: classes ที่สร้างมาล่วงหน้าสำหรับ CSS properties ทั่วไป
  2. Responsive Design: utilities สำหรับ responsive design ที่สร้างไว้แล้ว
  3. ปรับแต่งได้: ระบบการออกแบบที่กำหนดค่าได้สูง
  4. ประสิทธิภาพ: ลบ CSS ที่ไม่ใช้อัตโนมัติ
  5. ประสบการณ์นักพัฒนา: รองรับ IDE และเครื่องมือ debugging ที่เยี่ยม

ข้อดีของ Tailwind CSS:

  • การพัฒนาอย่างรวดเร็ว: สร้าง interfaces ได้เร็วโดยไม่ต้องเขียน custom CSS
  • การออกแบบที่สอดคล้อง: ระบบการออกแบบที่สร้างไว้แล้วรับประกันความสอดคล้อง
  • Mobile-First: responsive design ตั้งแต่ต้น
  • ขนาด Bundle เล็ก: รวมเฉพาะ CSS ที่คุณใช้จริง
  • ไม่ต้องเปลี่ยนบริบท: จัดแต่ง components โดยไม่ต้องออกจาก HTML

ตัวอย่างการใช้ Tailwind CSS:

<!-- วิธี CSS แบบดั้งเดิม -->
<div class="card">
  <h2 class="card-title">สวัสดีครับ</h2>
  <p class="card-description">นี่คือคำอธิบาย</p>
  <button class="btn btn-primary">คลิกที่นี่</button>
</div>

<!-- วิธี Tailwind CSS -->
<div class="bg-white p-6 rounded-lg shadow-md">
  <h2 class="text-2xl font-bold text-gray-900 mb-2">สวัสดีครับ</h2>
  <p class="text-gray-600 mb-4">นี่คือคำอธิบาย</p>
  <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
    คลิกที่นี่
  </button>
</div>

Tailwind CSS Classes ที่ใช้บ่อย:

  • Layout: flex, grid, container, mx-auto
  • Spacing: p-4 (padding), m-2 (margin), space-y-4
  • Typography: text-xl, font-bold, text-center
  • Colors: bg-blue-500, text-red-600, border-gray-300
  • Responsive: sm:text-lg, md:grid-cols-2, lg:px-8

อ้างอิง: Tailwind CSS Official Documentation

ASP.NET Core 9 Minimal API คืออะไร?

ASP.NET Core Minimal API เป็นแนวทางที่เรียบง่ายในการสร้าง HTTP APIs ด้วย ASP.NET Core เปิดตัวใน .NET 6 และปรับปรุงในเวอร์ชันต่อๆ มารวมถึง .NET 9 Minimal APIs ช่วยให้คุณสร้าง APIs ได้โดยใช้ code น้อยและไม่ซับซ้อน

คุณสมบัติหลักของ Minimal API:

  1. Boilerplate น้อย: เขียน code น้อยกว่าแบบ controllers ดั้งเดิม
  2. ประสิทธิภาพ: ประสิทธิภาพสูงด้วย overhead ที่น้อย
  3. Syntax เรียบง่าย: เข้าใจและเขียนง่าย
  4. คุณสมบัติที่สร้างไว้: model binding, validation และรองรับ OpenAPI อัตโนมัติ
  5. Hosting: ผสานรวมกับ ASP.NET Core hosting model

ข้อดีของ Minimal API:

  • การพัฒนาอย่างรวดเร็ว: สร้าง APIs ได้เร็วด้วยการตั้งค่าที่น้อย
  • เหมาะกับ Microservices: เหมาะสำหรับ services ขนาดเล็กที่มีจุดมุ่งหมายเฉพาะ
  • Cloud Native: เหมาะสำหรับสภาพแวดล้อม containerized
  • คุณสมบัติ C# สมัยใหม่: ใช้ประโยชน์จากคุณสมบัติล่าสุดของภาษา C#
  • การผสานรวม: ทำงานร่วมกับไลบรารี .NET อื่นๆ ได้อย่างราบรื่น

ตัวอย่าง Minimal API:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// เพิ่ม services ลงใน container
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlite("DataSource=app.db"));

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// กำหนดค่า HTTP request pipeline
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

// กำหนด API endpoints
app.MapGet("/api/products", async (AppDbContext db) =>
    await db.Products.ToListAsync())
    .WithName("GetProducts")
    .WithOpenApi();

app.MapGet("/api/products/{id}", async (int id, AppDbContext db) =>
    await db.Products.FindAsync(id) is Product product
        ? Results.Ok(product)
        : Results.NotFound())
    .WithName("GetProduct")
    .WithOpenApi();

app.MapPost("/api/products", async (Product product, AppDbContext db) =>
{
    db.Products.Add(product);
    await db.SaveChangesAsync();
    return Results.Created($"/api/products/{product.Id}", product);
})
.WithName("CreateProduct")
.WithOpenApi();

app.Run();

// Models
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public string Description { get; set; } = string.Empty;
}

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
    public DbSet<Product> Products { get; set; }
}

อ้างอิง: ASP.NET Core Minimal APIs Documentation

ตัวอย่างโปรเจค: E-commerce Product Catalog

มาสร้าง e-commerce product catalog อย่างง่ายที่แสดงให้เห็นว่าเทคโนโลยีทั้งหมดนี้ทำงานร่วมกันอย่างไรใน modular monolith architecture

โครงสร้างโปรเจค:

ECommerceApp/
├── frontend/                 # Vue.js + Tailwind CSS
│   ├── src/
│   │   ├── components/
│   │   ├── views/
│   │   ├── services/
│   │   └── main.js
│   ├── index.html
│   └── package.json
├── backend/                  # ASP.NET Core 9 Minimal API
│   ├── Modules/
│   │   ├── Products/
│   │   ├── Orders/
│   │   └── Users/
│   ├── Shared/
│   │   ├── Database/
│   │   └── Infrastructure/
│   ├── Program.cs
│   └── ECommerceApp.csproj
└── README.md

การติดตั้ง Frontend (Vue.js + Tailwind):

ProductList.vue:

<template>
  <div class="container mx-auto px-4 py-8">
    <h1 class="text-3xl font-bold text-gray-900 mb-8">แคตตาลอกสนค</h1>
    
    <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
      <div 
        v-for="product in products" 
        :key="product.id"
        class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow"
      >
        <div class="p-6">
          <h3 class="text-xl font-semibold text-gray-900 mb-2">
            {{ product.name }}
          </h3>
          <p class="text-gray-600 mb-4">{{ product.description }}</p>
          <div class="flex justify-between items-center">
            <span class="text-2xl font-bold text-blue-600">
              ฿{{ product.price }}
            </span>
            <button 
              @click="addToCart(product)"
              class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
            >
              เพิ่มลงตะกร
            </button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { productService } from '../services/productService'

const products = ref([])

const fetchProducts = async () => {
  try {
    const response = await productService.getAll()
    products.value = response.data
  } catch (error) {
    console.error('เกิดข้อผิดพลาดในการดึงข้อมูลสินค้า:', error)
  }
}

const addToCart = (product) => {
  // เพิ่มลงตะกร้า logic
  console.log('เพิ่มลงตะกร้า:', product)
}

onMounted(() => {
  fetchProducts()
})
</script>

Product Service:

// services/productService.js
import axios from 'axios'

const API_BASE_URL = 'https://localhost:7070/api'

export const productService = {
  async getAll() {
    return await axios.get(`${API_BASE_URL}/products`)
  },
  
  async getById(id) {
    return await axios.get(`${API_BASE_URL}/products/${id}`)
  },
  
  async create(product) {
    return await axios.post(`${API_BASE_URL}/products`, product)
  }
}

การติดตั้ง Backend (ASP.NET Core 9 Minimal API):

Program.cs:

using Microsoft.EntityFrameworkCore;
using ECommerceApp.Modules.Products;
using ECommerceApp.Modules.Orders;
using ECommerceApp.Shared.Database;

var builder = WebApplication.CreateBuilder(args);

// เพิ่ม services ลงใน container
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));

// เพิ่ม CORS
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowFrontend", policy =>
    {
        policy.WithOrigins("http://localhost:3000")
              .AllowAnyHeader()
              .AllowAnyMethod();
    });
});

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// ลงทะเบียน module services
builder.Services.AddProductModule();
builder.Services.AddOrderModule();

var app = builder.Build();

// กำหนดค่า HTTP request pipeline
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseCors("AllowFrontend");

// แมป module endpoints
app.MapProductEndpoints();
app.MapOrderEndpoints();

app.Run();

Product Module:

// Modules/Products/ProductModule.cs
using Microsoft.EntityFrameworkCore;
using ECommerceApp.Shared.Database;

namespace ECommerceApp.Modules.Products;

public static class ProductModule
{
    public static IServiceCollection AddProductModule(this IServiceCollection services)
    {
        services.AddScoped<IProductService, ProductService>();
        return services;
    }

    public static WebApplication MapProductEndpoints(this WebApplication app)
    {
        var group = app.MapGroup("/api/products").WithTags("Products");

        group.MapGet("/", GetProducts);
        group.MapGet("/{id}", GetProduct);
        group.MapPost("/", CreateProduct);
        group.MapPut("/{id}", UpdateProduct);
        group.MapDelete("/{id}", DeleteProduct);

        return app;
    }

    private static async Task<IResult> GetProducts(IProductService productService)
    {
        var products = await productService.GetAllAsync();
        return Results.Ok(products);
    }

    private static async Task<IResult> GetProduct(int id, IProductService productService)
    {
        var product = await productService.GetByIdAsync(id);
        return product != null ? Results.Ok(product) : Results.NotFound();
    }

    private static async Task<IResult> CreateProduct(Product product, IProductService productService)
    {
        var createdProduct = await productService.CreateAsync(product);
        return Results.Created($"/api/products/{createdProduct.Id}", createdProduct);
    }

    private static async Task<IResult> UpdateProduct(int id, Product product, IProductService productService)
    {
        if (id != product.Id) return Results.BadRequest();
        
        var updatedProduct = await productService.UpdateAsync(product);
        return updatedProduct != null ? Results.Ok(updatedProduct) : Results.NotFound();
    }

    private static async Task<IResult> DeleteProduct(int id, IProductService productService)
    {
        var success = await productService.DeleteAsync(id);
        return success ? Results.NoContent() : Results.NotFound();
    }
}

ทำไม Modular Monolith ถึงสำคัญ?

Modular Monolith รวมข้อดีของทั้ง monolithic และ microservices architectures ทำให้เป็นทางเลือกที่ยอดเยี่ยมสำหรับแอปพลิเคชันหลายประเภท โดยเฉพาะเมื่อเริ่มต้นโปรเจคใหม่

ข้อดีของ Modular Monolith:

  1. ความเรียบง่าย: deployment unit เดียว ง่ายต่อการพัฒนาและ debug
  2. ประสิทธิภาพ: ไม่มี network latency ระหว่าง modules
  3. ความสอดคล้อง: database transactions เดียวข้าม modules
  4. ความเร็วในการพัฒนา: การพัฒนาที่เร็วขึ้นด้วยการแชร์ code และไลบรารี
  5. การปรับปรุงง่าย: สามารถแยกเป็น microservices ทีหลังได้หากจำเป็น

หลักการสำคัญ:

  1. ขอบเขต Module ที่ชัดเจน: แต่ละ module มีความรับผิดชอบที่กำหนดไว้อย่างชัดเจน
  2. การเชื่อมต่อแบบหลวม: modules สื่อสารผ่าน interfaces ที่กำหนดไว้อย่างดี
  3. ความเหนียวแน่นสูง: functionality ที่เกี่ยวข้องอยู่ด้วยกันภายใน module
  4. Infrastructure ร่วม: เรื่องทั่วไปเช่น logging, configuration, database

เมื่อไรควรใช้ Modular Monolith:

  • เริ่มโปรเจคใหม่: เริ่มด้วย monolith แล้วแยกทีหลังหากจำเป็น
  • ทีมขนาดเล็กถึงกลาง: การประสานงานและการพัฒนาที่ง่ายขึ้น
  • ขอบเขต domain ไม่แน่นอน: เรียนรู้ domain ก่อนแยก
  • ความต้องการประสิทธิภาพ: latency ต่ำกว่าระบบแบบกระจาย
  • ความต้องการความสอดคล้อง: ต้องการ ACID transactions ข้าม modules

ตัวอย่างโครงสร้าง Module:

// Modules/Products/
├── ProductModule.cs          # การกำหนดค่า module และ endpoints
├── Models/
   ├── Product.cs           # Product entity
   └── ProductDto.cs        # Data transfer objects
├── Services/
   ├── IProductService.cs   # Service interface
   └── ProductService.cs    # Service implementation
└── Data/
    └── ProductRepository.cs # Data access layer

// Modules/Orders/
├── OrderModule.cs
├── Models/
├── Services/
└── Data/

// Shared/
├── Database/
   └── AppDbContext.cs      # Shared database context
├── Infrastructure/
   ├── Logging/
   └── Configuration/
└── Common/
    └── BaseEntity.cs        # Shared base classes

เส้นทางการย้ายไป Microservices:

หากแอปพลิเคชันของคุณเติบโตและคุณต้องการแยกเป็น microservices โครงสร้างแบบ modular ทำให้การเปลี่ยนแปลงนี้ง่ายขึ้น:

  1. ระบุขอบเขต Module: modules กลายเป็นขอบเขต service
  2. แยกฐานข้อมูล: ให้แต่ละ service มีฐานข้อมูลของตัวเอง
  3. เพิ่มชั้น Communication: แทนที่การเรียกตรงด้วย HTTP/messaging
  4. Deploy อิสระ: แต่ละ service ได้ deployment pipeline ของตัวเอง

สรุป

การสร้าง full stack applications ด้วย Vue.js, Tailwind CSS และ ASP.NET Core 9 Minimal API โดยใช้ Modular Monolith architecture ให้:

  • ประสบการณ์การพัฒนาสมัยใหม่: เทคโนโลยีล่าสุดและแนวปฏิบัติที่ดีที่สุด
  • การพัฒนาอย่างรวดเร็ว: การทำซ้ำและ deployment ที่รวดเร็ว
  • สถาปัตยกรรมที่ขยายได้: สามารถเติบโตจาก monolith ไป microservices
  • ประสบการณ์นักพัฒนาที่ยอดเยี่ยม: เครื่องมือและเอกสารที่ยอดเยี่ยม
  • ประสิทธิภาพ: แอปพลิเคชันที่รวดเร็วและมีประสิทธิภาพ

วิธีการนี้เหมาะสำหรับผู้เริ่มต้นเขียนโปรแกรมเพราะให้:

  • การแยกส่วนงานที่ชัดเจน
  • แนวปฏิบัติการพัฒนาสมัยใหม่
  • สถาปัตยกรรมที่ขยายได้
  • แหล่งเรียนรู้ที่ยอดเยี่ยม

เริ่มต้นด้วย stack นี้ เรียนรู้พื้นฐาน และค่อยขยายความรู้ของคุณเมื่อแอปพลิเคชันมีความซับซ้อนมากขึ้น

อ้างอิง

ถูกสร้างด้วย Hugo
ธีม Stack ออกแบบโดย Jimmy