21 - ออกแบบ Register
เป้าหมายของบท
Section titled “เป้าหมายของบท”บทนี้จะเริ่มภาค Login/Register ด้วย endpoint:
POST /api/v1/auth/registerหลังจบบทนี้ผู้อ่านควรเข้าใจ:
- register flow ควรตรวจอะไรบ้าง
- ทำไม register ไม่ควรอยู่ใน
UserController - ใช้
RegisterRequestและUserResponseอย่างไร - default role/status ควรกำหนดที่ฝั่ง server
- ทำไม response ห้ามมี password
Register flow
Section titled “Register flow”flow สมัครสมาชิกควรเป็นแบบนี้:
Client -> POST /api/v1/auth/register -> AuthController -> AuthService -> UserRepository -> PostgreSQLขั้นตอนใน service:
- รับ
username,email,password - validate request body
- เช็กว่า username ซ้ำหรือไม่
- เช็กว่า email ซ้ำหรือไม่
- hash password
- สร้าง user พร้อม role/status เริ่มต้น
- save ลง database
- ส่ง
UserResponseกลับโดยไม่มี password
ทำไมใช้ AuthController
Section titled “ทำไมใช้ AuthController”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สร้าง RegisterRequest
Section titled “สร้าง RegisterRequest”สร้างไฟล์:
src/main/java/com/example/backendapi/dto/RegisterRequest.javapackage 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) {}สร้าง AuthController
Section titled “สร้าง AuthController”สร้างไฟล์:
src/main/java/com/example/backendapi/controller/AuthController.javapackage 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")@RequiredArgsConstructorpublic class AuthController {
private final AuthService authService;
@PostMapping("/register") public ApiResponse<UserResponse> register( @Valid @RequestBody RegisterRequest request ) { return ApiResponse.ok("User registered", authService.register(request)); }}สร้าง AuthService
Section titled “สร้าง AuthService”สร้างไฟล์:
src/main/java/com/example/backendapi/service/AuthService.javapackage 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@RequiredArgsConstructorpublic 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 ทันที
Request ตัวอย่าง
Section titled “Request ตัวอย่าง”POST /api/v1/auth/registerContent-Type: application/json{ "username": "john", "email": "john@example.com", "password": "password123"}Response ตัวอย่าง
Section titled “Response ตัวอย่าง”{ "success": true, "message": "User registered", "data": { "id": 1, "username": "john", "email": "john@example.com", "role": "USER", "status": "ACTIVE" }, "timestamp": "2026-06-15T10:00:00"}ข้อควรระวัง
Section titled “ข้อควรระวัง”- role เริ่มต้นควรกำหนดที่ backend ไม่ใช่ให้ client ส่งมา
- status เริ่มต้นควรกำหนดที่ backend
- response ห้ามส่ง password
- error username/email ซ้ำควรเป็น
409 Conflict - password ต้อง hash ก่อนบันทึกจริงในบทถัดไป
แบบฝึกหัดท้ายบท
Section titled “แบบฝึกหัดท้ายบท”- สร้าง
RegisterRequest - สร้าง
AuthController - สร้าง
AuthService - ทดสอบ register สำเร็จ
- ทดสอบ username ซ้ำ
- ทดสอบ email ซ้ำ