Express TypeORM lookup filter (like django)

Chung Hwan·2021년 8월 9일
0
post-thumbnail

api가 query(GET params)를 수행할 수 있으면 좋은 점이 많다. 프론트엔드 개발이 훨씬 편해지고, 불필요한 정보를 가져올 필요가 없어지므로 퍼포먼스가 향상된다. 사실 거의 필수적이라고 보는게 맞겠다.
django의 경우는 django-filter가 있어서 query를 쉽게 처리할 수 있다. 왠지 express(typeorm을 사용함)에도 비슷한게 있을 것 같지만, 직접 만드는 게 재밌을 것 같아서 만들어보았다.

목표

이런 식으로 쓸 수 있는 lookup을 만들고 싶었다.

const drops = await Drop.find(
  lookup<Drop>({
    title: { lookup: "contains", value: title },
    artist: artist,
  })
);

types

우선 타입을 정의한다.

export type Lookup = "exact" | "contains";

export type LookupRecord = { lookup: Lookup; value: string | undefined };
export type ValueOf<T> = T[keyof T];

export type Filter<T> = {
  [key in keyof T]?: LookupRecord | ValueOf<T>;
};
  • 가능한 lookup의 종류가 현재 exact혹은contains이다. 언제든 확장가능하다.
  • LookupRecord는 lookup의 종류, 그리고 query 값을 가진다.
  • Filter는 key마다 LookupRecord 혹은 generic T에 대해 T의 value type을 가진다. ValueOf<T>가 온 경우에는 기본적으로 exact로 lookup을 수행한다.

isLookupRecord

function isLookupRecord<T>(record: LookupRecord | ValueOf<T> | undefined): record is LookupRecord {
  return (record as LookupRecord).lookup !== undefined;
}

일단 Filter의 value가 ValueOf<T> 인지 LookupRecord인지 구분하기 위한 isLookupRecord함수를 만들었다.

removeEmpty

export function removeEmpty<T>(obj: KeyValue<T>): { [key: string]: ValueOf<T> } {
  const result: { [key: string]: ValueOf<T> } = {};
  Object.keys(obj).forEach((key) => obj[key as keyof T] && (result[key] = obj[key as keyof T]));
  return result;
}

Object에서 undefined, null 인 value들을 없애주는 함수다. query로 key가 들어올지 안 올지 모르기 때문에 필요하다.

lookup

export default function lookup<T>(filter: Filter<T>) {
  const result: { [key: string]: unknown } = {};
  Object.keys(filter).forEach((key) => {
    const record = filter[key as keyof T];
    if (record === undefined) return;
    if (isLookupRecord<T>(record)) {
      switch (record.lookup) {
      case "exact":
        result[key] = record.value;
        break;
      case "contains":
        result[key] = contains(record.value);
        break;
      }
    } else {
      result[key] = record;
    }
  });
  return removeEmpty(result);
}

Filter의 key를 iterate한다.

  • ValueOf<T>인 경우
    lookup을 보고 이에 맞는 lookup을 수행한다.
  • LookupRecord인 경우
    exact로 수행한다.

결과를 clean up해서 return한다.

module

import { ILike } from "typeorm";

export const contains = (value?: string) => (value ? ILike(`%${value}%`) : ILike("%"));

이제 lookup을 실제로 해줄 쿼리를 함수 형태로 만들어준다.

implement

controller 단에서 lookup을 명시적으로 해줄 수 있다.

  listDrop: async (req: Request, res: Response) => {
    const title = req.query.title ? `${req.query.title}` : undefined;
    const artist = req.query.artist ? await User.findOneByUsername(`${req.query.artist}`) : undefined;
    const drops = await Drop.find(
      lookup<Drop>({
        title: { lookup: "contains", value: title },
        artist: artist,
      })
    );
    return res.status(200).json(drops);
  },

와우!

0개의 댓글