Blog

[NestJS] 12. Kakao 다음 우편번호 API 주소 입력 추가

Category
Author
citeFred
citeFred
PinOnMain
1 more property
NestJS, TypeORM 이해하기
Table of Content

1. 다음 우편번호 API 서비스 활용하기

해당 부분은 프론트엔드에서 처리할 로직이 대부분이다.
하지만 해당 API 기능을 사용하게 된다면 서버에서도 주소 관련 데이터를 받을 준비를 해야 한다.
반환되는 데이터 확인하기
기본적으로 이런 외부 API의 경우 이미 정해진 변수명, 타입 등으로 데이터를 JSON 형태로 내보내준다.
Kakao 주소검색 API 에서 반환해주는 데이터의 예시는 다음과 같다.
여기서 중요한 Key, Value는 다음과 같다.
zonecode : 우편번호 (* 구버전에서는 postcode였으나 변경됨)
address : 주소 (한글주소)
여기까지는 주소 검색을 통해서 제공 될 부분
주소에서 세부 주소(동호수 등)이 필요하기 때문에 detailAddress 입력 필드가 추가로 필요하다.
프론트엔드에서 고려할 점
API가 제공해주는 데이터의 postcode, postalcode같은 명칭을 기대했으나 zonecode라는 명칭이 사용되고 있다.
이는 API 사용자인 프론트엔드 측에서 좀 더 직관적으로 변경해주는것이 옳다고 판단된다.
변수 명 등은 백엔드와 소통하여 결정짓도록 한다.
const data = { postalCode: response.zonecode, // API에서 받은 zonecode를 postalCode로 변환 address: response.address, detailAddress: userInputDetailAddress // 사용자가 입력한 상세주소 };
TypeScript
복사
detailAddress 세부 주소는 사용자 입력 필드의 값을 가져오도록 해야 한다.
이 또한 주소 입력 부분은 주소 찾기 API 팝업으로 이동되도록 강제하고 입력이 끝난 이후엔 자동 입력, 이후 세부 주소 입력 필드로 커서를 이동 시키는 등 UX를 고려하여 설계하면 좋다.
Angular 소스코드 참고자료
user.entity.ts에서 주소관련 필드 추가하기
기본적인 postalCode, address, detailAddress 정도로 구분
주소를 통해서 특별한 부분 조회 등 기능은 기획하지 않고 있기 때문에 시스템을 간소화하는 목적으로 시/군/구/동 등의 정규화는 제외했다.
프론트엔드에서 보내오는 데이터의 명칭이 다른것은 DTO를 통해서 해결하면되기 때문에 백엔드에서는 최대한 아래와 같이 직관적인 명명규칙을 사용하는 것이 좋다.
이는 프론트엔드와 백엔드가 최대한 명명규칙을 통일하는 것이 커뮤니케이션 혼란을 막을 수 있다.
실제로 zipcode, addr1, addr2 같은 변수명도 관례적으로 자주 사용하기 때문에 고려할 대상이다.
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { Article } from "src/article/article.entity"; import { UserRole } from "./user-role.enum"; import { BaseEntity } from "src/common/base.entity"; @Entity() export class User extends BaseEntity{ @Column() username: string; @Column() password: string; @Column({ unique: true }) // 이메일은 중복되지 않도록 한다. email: string; @Column() role: UserRole; @Column({ nullable: true }) postalCode: string; @Column({ nullable: true }) address: string; @Column({ nullable: true }) detailAddress: string; @OneToMany(Type => Article, article => article.author, { eager: false }) articles: Article[]; }
TypeScript
복사
sign-up-request.dto.ts
Daum 주소 API를 통해 제공되는 주소는 이미 검증된 데이터이므로, 백엔드에서 과도한 주소 유효성 검사는 필요하지 않을 수 있다.
하지만 사용자가 직접 입력하는 detailAddress 부분에는 유효성 검사가 필요할 수 있다.
다른 필드들도 기본적인 유효성 검사만 추가했다.
import { IsEmail, IsEnum, IsNotEmpty, Matches, MaxLength, MinLength } from "class-validator"; import { UserRole } from "../../user/user-role.enum"; export class SignUpRequestDto { @IsNotEmpty() // null 값 체크 @MinLength(2) // 최소 문자 수 @MaxLength(20) // 최대 문자 수 // @IsAlphanumeric() // 영문 알파벳만 허용일 경우 @Matches(/^[가-힣]+$/, { message: 'Username must be in Korean characters', }) // 한글 이름인 경우 username: string; @IsNotEmpty() @MaxLength(20) @Matches(/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, { message: 'Password too weak', }) // 대문자, 소문자, 숫자, 특수문자 포함 password: string; @IsNotEmpty() @IsEmail() // 이메일 형식 @MaxLength(100) email: string; @IsNotEmpty() @IsEnum(UserRole) // 열거형 UserRole에 포함된 상태만 허용, USER / ADMIN role: UserRole; @IsNotEmpty() @Matches(/^\d{5}$/, { message: 'Postal code must be 5 digits' }) // 우편번호는 5자리 숫자 postalCode: string; @IsNotEmpty() @MaxLength(100) // 주소는 최대 100자 address: string; @MaxLength(100, { message: 'Detail address is too long' }) // 상세 주소는 선택적으로 최대 100자 detailAddress: string; }
TypeScript
복사
auth.service.ts
회원 가입 시 주소 필드들을 가진 엔터티 객체가 생성되도록 필드 추가
import { ConflictException, Injectable, UnauthorizedException, Logger, Res } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { User } from "src/user/user.entity"; import { Repository } from 'typeorm'; import { SignUpRequestDto } from './dto/sign-up-request.dto'; import * as bcrypt from 'bcryptjs'; import { SignInRequestDto } from './dto/sign-in-request.dto'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class AuthService { private readonly logger = new Logger(AuthService.name); constructor( @InjectRepository(User) private usersRepository: Repository<User>, private jwtService: JwtService ){} // 회원 가입 async signUp(signUpRequestDto: SignUpRequestDto): Promise<User> { const { username, password, email, role, postalCode, address, detailAddress } = signUpRequestDto; this.logger.verbose(`Attempting to sign up user with email: ${email}`); // 이메일 중복 확인 await this.checkEmailExists(email); // 비밀번호 해싱 const hashedPassword = await this.hashPassword(password); const user = this.usersRepository.create({ username, password: hashedPassword, // 해싱된 비밀번호 사용 email, role, postalCode, address, detailAddress, }); const savedUser = await this.usersRepository.save(user); this.logger.verbose(`User signed up successfully with email: ${email}`); this.logger.debug(`User details: ${JSON.stringify(savedUser)}`); return savedUser; } // 로그인 ... }
TypeScript
복사
user-response.dto
주소를 기입한 회원이 주소 정보를 가지고 오는지 확인하기 위하여 추가
import { UserRole } from "src/user/user-role.enum"; import { User } from "src/user/user.entity"; export class UserResponseDto { id: number; username: string; email: string; role: UserRole; createdAt: Date; updatedAt: Date; postalCode: string; address: string; detailAddress: string; constructor(user: User){ this.id = user.id; this.username = user.username; this.email = user.email; this.role = user.role; this.createdAt = user.createdAt; this.updatedAt = user.updatedAt; this.postalCode = user.postalCode; this.address = user.address; this.detailAddress = user.detailAddress; } }
TypeScript
복사
POSTMAN을 통한 테스트
Search
 | Main Page | Category |  Tags | About Me | Contact | Portfolio