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 + 타입추론