NestJS Enterprise Backend APIs · บทเรียน

การดำเนินการ CRUD ด้วย TypeORM

พัฒนาฟังก์ชันสร้าง อ่าน อัปเดต และลบข้อมูลอย่างครบถ้วนสำหรับเอนทิตีของคุณโดยใช้ที่เก็บข้อมูลของ TypeORM

บทเรียน 2 จาก 311 ขั้นตอน

การดำเนินการ CRUD ด้วย TypeORM เป็นบทเรียน NestJS Enterprise Backend APIs ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 3 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน NestJS Enterprise Backend APIs และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส NestJS Enterprise Backend APIs มีบทเรียนทั้งหมด 3 บทเรียน

บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ

CRUD Basics with TypeORM

Welcome! In this lesson, we'll master CRUD operations using TypeORM, a powerful Object-Relational Mapper for TypeScript.

CRUD stands for Create, Read, Update, and Delete. These are the four fundamental operations for managing data in almost any application.

TypeORM simplifies interacting with your database by mapping database rows to TypeScript objects, making these operations intuitive.

Defining Our Product Entity

Before performing CRUD, we need an entity. An entity is a class that maps directly to a database table. Let's imagine a simple Product entity for our examples.

In TypeORM, you'd use decorators like @Entity(), @PrimaryGeneratedColumn(), and @Column() to define its structure and how it maps to your database schema.

Understanding the Repository

TypeORM uses the repository pattern. A repository is like a specialized collection that handles data access for a specific entity (e.g., a ProductRepository for Product entities).

You inject this repository into your services and use its methods to perform CRUD operations. It abstracts away the complex database queries.

Create: Adding New Products

To add new data to your database, you primarily use the .save() method of your repository. It intelligently handles both new insertions and updates to existing entities.

Here's a simplified example of creating a new product:

class Product {
  id: number;
  name: string;
  price: number;
  constructor(name: string, price: number, id: number = 0) {
    this.id = id;
    this.name = name;
    this.price = price;
  }
}

class MockProductRepository {
  private products: Product[] = [];
  private nextId = 1;

  async save(product: Product): Promise<Product> {
    if (product.id === 0) { // Simulate new product
      product.id = this.nextId++;
      this.products.push(product);
    } else { // Simulate update
      const index = this.products.findIndex(p => p.id === product.id);
      if (index > -1) { this.products[index] = product; }
    }
    return product;
  }

  async find(): Promise<Product[]> {
    return this.products;
  }
}

async function main() {
  const repo = new MockProductRepository();
  console.log("--- Creating a Product ---");
  const newProduct = await repo.save(new Product("Keyboard", 75));
  console.log("Created:", newProduct);

  console.log("\n--- Current Products ---");
  const allProducts = await repo.find();
  console.log(allProducts);
}

main();

Read: Fetching Products

Reading data is fundamental. TypeORM repositories offer several methods to retrieve entities:

  • .find(): Retrieves all entities matching given criteria.
  • .findOneBy(): Retrieves a single entity by a simple condition (e.g., by ID).
  • .findBy(): Retrieves multiple entities by simple conditions.

Let's see how to find products:

class Product {
  id: number;
  name: string;
  price: number;
  constructor(name: string, price: number, id: number = 0) {
    this.id = id;
    this.name = name;
    this.price = price;
  }
}

class MockProductRepository {
  private products: Product[] = [];
  private nextId = 1;

  async save(product: Product): Promise<Product> {
    if (product.id === 0) { product.id = this.nextId++; this.products.push(product); }
    else { const index = this.products.findIndex(p => p.id === product.id);
      if (index > -1) { this.products[index] = product; } }
    return product;
  }

  async find(): Promise<Product[]> { return this.products; }
  async findOneBy(criteria: Partial<Product>): Promise<Product | undefined> {
    return this.products.find(p =>
      Object.keys(criteria).every(key => p[key as keyof Product] === criteria[key as keyof Product])
    );
  }
}

async function main() {
  const repo = new MockProductRepository();
  const product1 = await repo.save(new Product("Mouse", 25));
  const product2 = await repo.save(new Product("Monitor", 300));

  console.log("--- Finding All Products ---");
  let allProducts = await repo.find();
  console.log("All:", allProducts);

  console.log("\n--- Finding One Product by ID ---");
  const foundProduct = await repo.findOneBy({ id: product1.id });
  console.log("Found by ID:", foundProduct);

  console.log("\n--- Finding One Product by Name ---");
  const namedProduct = await repo.findOneBy({ name: "Monitor" });
  console.log("Found by Name:", namedProduct);
}

main();

Update: Modifying Product Details

To modify existing data, you have two primary approaches:

  • Fetch, Modify, Save: Retrieve an entity, update its properties, then call .save() on the modified entity.
  • Direct Update: Use .update(criteria, partialEntity) to update specific columns for entities matching certain criteria without loading them into memory first. This is more efficient for mass updates.
class Product {
  id: number;
  name: string;
  price: number;
  constructor(name: string, price: number, id: number = 0) {
    this.id = id;
    this.name = name;
    this.price = price;
  }
}

class MockProductRepository {
  private products: Product[] = [];
  private nextId = 1;

  async save(product: Product): Promise<Product> {
    if (product.id === 0) { product.id = this.nextId++; this.products.push(product); }
    else { const index = this.products.findIndex(p => p.id === product.id);
      if (index > -1) { this.products[index] = product; } }
    return product;
  }

  async findOneBy(criteria: Partial<Product>): Promise<Product | undefined> {
    return this.products.find(p =>
      Object.keys(criteria).every(key => p[key as keyof Product] === criteria[key as keyof Product])
    );
  }
  
  async update(id: number, partialEntity: Partial<Product>): Promise<void> {
    const index = this.products.findIndex(p => p.id === id);
    if (index > -1) {
      this.products[index] = { ...this.products[index], ...partialEntity };
    }
  }
}

async function main() {
  const repo = new MockProductRepository();
  const product = await repo.save(new Product("Headphones", 150));
  console.log("Initial product:", product);

  console.log("\n--- Updating Product Price ---");
  let fetchedProduct = await repo.findOneBy({ id: product.id });
  if (fetchedProduct) {
    fetchedProduct.price = 140; 
    await repo.save(fetchedProduct);
  }
  console.log("Updated via save:", await repo.findOneBy({ id: product.id }));

  const product2 = await repo.save(new Product("Webcam", 80));
  console.log("Initial product 2:", product2);
  await repo.update(product2.id, { price: 70, name: "HD Webcam" });
  console.log("Updated via update:", await repo.findOneBy({ id: product2.id }));
}

main();

Delete: Removing Products

To remove entities from the database, TypeORM provides .delete() and .remove():

  • .delete(criteria): Removes entities by ID or specific conditions. It's efficient as it doesn't load entities into memory first.
  • .remove(entityOrEntities): Removes entities that are already loaded into memory (i.e., you have the full entity object).

Let's remove a product from our mock database:

class Product {
  id: number;
  name: string;
  price: number;
  constructor(name: string, price: number, id: number = 0) {
    this.id = id;
    this.name = name;
    this.price = price;
  }
}

class MockProductRepository {
  private products: Product[] = [];
  private nextId = 1;

  async save(product: Product): Promise<Product> {
    if (product.id === 0) { product.id = this.nextId++; this.products.push(product); }
    else { const index = this.products.findIndex(p => p.id === product.id);
      if (index > -1) { this.products[index] = product; } }
    return product;
  }

  async find(): Promise<Product[]> { return this.products; }

  async delete(id: number): Promise<void> {
    this.products = this.products.filter(p => p.id !== id);
  }
  
  async remove(product: Product): Promise<Product> {
    this.products = this.products.filter(p => p.id !== product.id);
    return product; 
  }
}

async function main() {
  const repo = new MockProductRepository();
  const productA = await repo.save(new Product("Pen", 2));
  const productB = await repo.save(new Product("Notebook", 5));
  console.log("Initial products:", await repo.find());

  console.log("\n--- Deleting Product A by ID ---");
  await repo.delete(productA.id);
  console.log("Products after delete:", await repo.find());

  console.log("\n--- Removing Product B (by entity) ---");
  await repo.remove(productB);
  console.log("Products after remove:", await repo.find());
}

main();

CRUD in a NestJS Service

In a real NestJS application, you'd integrate these repository methods within a service. The service would then expose higher-level methods that perform specific business logic, calling the underlying repository methods.

This keeps your controllers clean, focusing only on handling HTTP requests and delegating data operations to the service layer.

// products.service.ts (simplified NestJS service)
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Product } from './product.entity'; // Your entity

@Injectable()
export class ProductsService {
  constructor(
    @InjectRepository(Product)
    private productsRepository: Repository<Product>,
  ) {}

  async create(productData: Partial<Product>): Promise<Product> {
    const newProduct = this.productsRepository.create(productData);
    return this.productsRepository.save(newProduct);
  }

  async findAll(): Promise<Product[]> {
    return this.productsRepository.find();
  }

  async findOne(id: number): Promise<Product | undefined> {
    return this.productsRepository.findOneBy({ id });
  }

  async update(id: number, productData: Partial<Product>): Promise<Product | undefined> {
    await this.productsRepository.update(id, productData);
    return this.findOne(id); 
  }

  async remove(id: number): Promise<void> {
    await this.productsRepository.delete(id);
  }
}

Robust CRUD: Best Practices

Building robust CRUD operations involves more than just calling repository methods:

  • Data Validation: Use Data Transfer Objects (DTOs) and validation pipes to ensure incoming data is valid (covered in A2 L2).
  • Error Handling: Gracefully handle cases like 'entity not found' by throwing appropriate exceptions (e.g., NestJS's NotFoundException).
  • Transactions: For complex operations involving multiple database changes, use transactions (covered in C1 L2) to ensure data consistency.

CRUD Operation Quiz

Which TypeORM repository method is typically used to update only specific fields of an entity without loading the entire entity into memory first?

Recap: Mastering CRUD

Great job! You've learned the essentials of performing CRUD operations using TypeORM repositories.

  • Create: Use .save() to add new entities.
  • Read: Use .find(), .findOneBy() to retrieve entities.
  • Update: Modify and .save(), or use .update() for partial updates.
  • Delete: Use .delete() or .remove() to eliminate entities.

These skills are fundamental for building any data-driven API with NestJS and TypeORM. Next, we'll explore error handling and interceptors to make your APIs more robust!

เริ่มต้นได้ฟรี

เรียนรู้ TypeScript ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
20
บทเรียน
76

คำถามที่พบบ่อย

บทเรียน “การดำเนินการ CRUD ด้วย TypeORM” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การดำเนินการ CRUD ด้วย TypeORM” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส NestJS Enterprise Backend APIs ให้อัปเกรดเป็น CoddyKit PRO คอร์ส NestJS Enterprise Backend APIs มีบทเรียนทั้งหมด 3 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การดำเนินการ CRUD ด้วย TypeORM”

พัฒนาฟังก์ชันสร้าง อ่าน อัปเดต และลบข้อมูลอย่างครบถ้วนสำหรับเอนทิตีของคุณโดยใช้ที่เก็บข้อมูลของ TypeORM คุณปฏิบัติ NestJS Enterprise Backend APIs ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน NestJS Enterprise Backend APIs หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน NestJS Enterprise Backend APIs บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 3 บทเรียน

บทเรียน “การดำเนินการ CRUD ด้วย TypeORM” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน NestJS Enterprise Backend APIs นี้ได้ไหม

ได้ บทเรียน NestJS Enterprise Backend APIs ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. เส้นทางและการจัดการคำขอ
  2. การดำเนินการ CRUD ด้วย TypeORM
  3. การจัดการข้อผิดพลาดและอินเตอร์เซปเตอร์
← กลับไปที่ NestJS Enterprise Backend APIs