Skip to content

21 - ออกแบบ Register

บทนี้จะเริ่มภาค Login/Register ด้วย endpoint:

POST /api/v1/auth/register

หลังจบบทนี้ผู้อ่านควรเข้าใจ:

  • register flow ควรตรวจอะไรบ้าง
  • ทำไม register ไม่ควรอยู่ใน UserController
  • ใช้ RegisterRequest และ UserResponse อย่างไร
  • default role/status ควรกำหนดที่ฝั่ง server
  • ทำไม response ห้ามมี password

flow สมัครสมาชิกควรเป็นแบบนี้:

Client
-> POST /api/v1/auth/register
-> AuthController
-> AuthService
-> UserRepository
-> PostgreSQL

ขั้นตอนใน service:

  1. รับ username, email, password
  2. validate request body
  3. เช็กว่า username ซ้ำหรือไม่
  4. เช็กว่า email ซ้ำหรือไม่
  5. hash password
  6. สร้าง user พร้อม role/status เริ่มต้น
  7. save ลง database
  8. ส่ง UserResponse กลับโดยไม่มี password

UserController เหมาะกับ CRUD user

AuthController เหมาะกับงาน authentication เช่น:

  • register
  • login
  • me/current user

แยกแบบนี้ทำให้ endpoint อ่านง่าย:

/api/v1/users
/api/v1/auth/register
/api/v1/auth/login
/api/v1/auth/me

สร้างไฟล์:

src/main/java/com/example/backendapi/dto/RegisterRequest.java
package com.example.backendapi.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record RegisterRequest(
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be 3-50 characters")
String username,
@NotBlank(message = "Email is required")
@Email(message = "Email format is invalid")
String email,
@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
String password
) {
}

สร้างไฟล์:

src/main/java/com/example/backendapi/controller/AuthController.java
package com.example.backendapi.controller;
import com.example.backendapi.common.ApiResponse;
import com.example.backendapi.dto.RegisterRequest;
import com.example.backendapi.dto.UserResponse;
import com.example.backendapi.service.AuthService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/register")
public ApiResponse<UserResponse> register(
@Valid @RequestBody RegisterRequest request
) {
return ApiResponse.ok("User registered", authService.register(request));
}
}

สร้างไฟล์:

src/main/java/com/example/backendapi/service/AuthService.java
package com.example.backendapi.service;
import com.example.backendapi.dto.RegisterRequest;
import com.example.backendapi.dto.UserResponse;
import com.example.backendapi.exception.DuplicateUserException;
import com.example.backendapi.model.User;
import com.example.backendapi.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserRepository userRepository;
public UserResponse register(RegisterRequest request) {
if (userRepository.existsByUsername(request.username())) {
throw new DuplicateUserException("Username already exists");
}
if (userRepository.existsByEmail(request.email())) {
throw new DuplicateUserException("Email already exists");
}
User user = new User();
user.setUsername(request.username());
user.setEmail(request.email());
user.setPassword(request.password());
return toResponse(userRepository.save(user));
}
private UserResponse toResponse(User user) {
return new UserResponse(
user.getId(),
user.getUsername(),
user.getEmail(),
user.getRole(),
user.getStatus(),
user.getCreatedAt(),
user.getUpdatedAt()
);
}
}

บทนี้ยังตั้ง password ตรง ๆ ชั่วคราวเพื่อให้เห็น register flow ก่อน บทถัดไปจะเปลี่ยนเป็น password hash ทันที

POST /api/v1/auth/register
Content-Type: application/json
{
"username": "john",
"email": "john@example.com",
"password": "password123"
}
{
"success": true,
"message": "User registered",
"data": {
"id": 1,
"username": "john",
"email": "john@example.com",
"role": "USER",
"status": "ACTIVE"
},
"timestamp": "2026-06-15T10:00:00"
}
  • role เริ่มต้นควรกำหนดที่ backend ไม่ใช่ให้ client ส่งมา
  • status เริ่มต้นควรกำหนดที่ backend
  • response ห้ามส่ง password
  • error username/email ซ้ำควรเป็น 409 Conflict
  • password ต้อง hash ก่อนบันทึกจริงในบทถัดไป

แบบฝึกหัดท้ายบท

Section titled “แบบฝึกหัดท้ายบท”
  1. สร้าง RegisterRequest
  2. สร้าง AuthController
  3. สร้าง AuthService
  4. ทดสอบ register สำเร็จ
  5. ทดสอบ username ซ้ำ
  6. ทดสอบ email ซ้ำ