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,
})
);
우선 타입을 정의한다.
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>;
};
exact
혹은contains
이다. 언제든 확장가능하다.LookupRecord
는 lookup의 종류, 그리고 query 값을 가진다.Filter
는 key마다 LookupRecord
혹은 generic T에 대해 T의 value type을 가진다. ValueOf<T>
가 온 경우에는 기본적으로 exact
로 lookup을 수행한다.function isLookupRecord<T>(record: LookupRecord | ValueOf<T> | undefined): record is LookupRecord {
return (record as LookupRecord).lookup !== undefined;
}
일단 Filter
의 value가 ValueOf<T>
인지 LookupRecord
인지 구분하기 위한 isLookupRecord
함수를 만들었다.
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가 들어올지 안 올지 모르기 때문에 필요하다.
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>
인 경우LookupRecord
인 경우결과를 clean up해서 return한다.
import { ILike } from "typeorm";
export const contains = (value?: string) => (value ? ILike(`%${value}%`) : ILike("%"));
이제 lookup을 실제로 해줄 쿼리를 함수 형태로 만들어준다.
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);
},
와우!