Project
SuperStruct
codingtori
2025. 4. 4. 11:54
왜 Superstruct 를 사용할까?
- node.js + express 환경 → express는 입력 데이터 검증을 안해줌
- node.js+express는 서버 환경이고, 대부분의 요청은 외부 클라이언트로 부터 옴 = 이 데이터는 믿을 수 없음
ex) email 자리에 숫자 넣기, users POST요청에서 body에 이름이 빠져있는 등..
- typescript → 런타임 검증을 못함 (컴파일 타임에만 체크)→ 실제 api 요청으로 무엇이 들어올 지 모름
Superstruct
: 런타임에서 들어오는 데이터를 구조화 (structrue)해서 유효성 검사할 수 있게 해주는 라이브러리 = 런타임 스키마 검증 도구
- 데이터가 기대한 타입인지
- 필수 필드가 빠지지 않았는지
- 값의 길이나 범위가 적절한지
- 중첩된 구조까지 제대로 검증하는지
방식
- s.object, s.string, s.min, s.enums 등으로 구조(schema)를 만들고
- assert로 실제 데이터가 이 구조를 따르는지 검증함
설치법
npm install superstruct is-email is-uuid
미들웨어 처럼 사용도 가능
const validate = (schema) => (req, res, next) => {
try {
assert(req.body, schema);
next();
} catch (err) {
res.status(400).json({ error: err.message });
}
};
//사용
router.post('/users', validate(CreateUser), createUserHandler);
- 단순 타입 체크를 넘어서, min, max, enum, uuid, custom validator 도 가능
- partial, optional, nullable 도 가능
- 중첩된 객체, 배열도 편하게 다룰 수 있음
const Product = object({
id: define('Uuid', isUuid.v4),
name: string(),
tags: s.array(string()),
price: s.min(s.number(), 0),
metadata: s.optional(object({
manufacturer: string(),
warranty: s.optional(string()),
})),
});
구조화 방식
project/
├── validators/
│ ├── user.validator.js ← superstruct 스키마들
│ └── product.validator.js
├── routes/
│ └── user.route.js
├── controllers/
│ └── user.controller.js
위 구조에 따른 실제 사용 예
- validators/user.validator.js
import { object, string, define } from 'superstruct';
import isEmail from 'is-email';
const Email = define('Email', isEmail);
export const CreateUser = object({
email: Email,
firstName: string(),
lastName: string(),
});
- routes/user.route.js
import express from 'express';
import { assert } from 'superstruct';
import { CreateUser } from '../validators/user.validator';
const router = express.Router();
router.post('/users', (req, res) => {
try {
assert(req.body, CreateUser);
// Controller로 넘기거나 직접 처리
res.send({ message: 'User created' });
} catch (err) {
res.status(400).send({ error: err.message });
}
});
superstruct 는 모든 라우터에 적용할 필요는 없음
입력 데이터(body, query, param 등)이 있는 POST, PUT, PATCH에 집중해서 사용
- 보통 라우터 별로 schema 정의해서 사용함
유효성 검사는 라우터에서 하는 것이 일반적
- 핵심 로직에 들어가기 전에 걸러야하는 작업 → 컨트롤러 안으로 invalid한 데이터가 못 들어오게 하는 게 좋음
따라서 보통 실무에서는
- schemas/ 폴더에 모든 struct들 정리
- middlewares/validateBody.js 같은 검증 미들웨어 생성
- routes/에서 route-level에서 붙여줌
이렇게 구조를 생성한다고 한다
src/
├── routes/
│ └── products.js
├── schemas/
│ └── product.schema.js
├── middlewares/
│ └── validateBody.js
product.schema.js 예시
const s = require('superstruct');
const { object, size, string, enums, min, number, integer, partial } = s;
const CATEGORIES = [
'FASHION',
'BEAUTY',
'SPORTS',
'ELECTRONICS',
'HOME_INTERIOR',
'HOUSEHOLD_SUPPLIES',
'KITCHENWARE',
];
// ✅ 상품 생성 시 사용하는 스키마
const CreateProductSchema = object({
name: size(string(), 1, 60),
description: string(),
category: enums(CATEGORIES),
price: min(number(), 0),
stock: min(integer(), 0),
});
// ✅ 상품 수정 시 사용하는 스키마 (부분 수정 허용)
const PatchProductSchema = partial(CreateProductSchema);
module.exports = {
CreateProductSchema,
PatchProductSchema,
CATEGORIES,
};
위에서 validator 의 경우도 있고 schema의 경우도 있는데 둘의 차이가 조금 존재한다
- validator 은 단순 유효성 검증에 집중
- schema는 validation + 타입추론