diff --git a/src/constants/columnMapping.js b/src/constants/columnMapping.js
new file mode 100644
index 0000000000000000000000000000000000000000..9997dea9bf69e56c2345401dbe8259a5ffb456a8
--- /dev/null
+++ b/src/constants/columnMapping.js
@@ -0,0 +1,58 @@
+export const columnMapping = {
+  cpu: {
+    family_type: 'CPU 종류',
+    socket_type: '소켓 구분',
+    core_count: '코어 수',
+    thread_count: '스레드 수',
+    base_clock: '기본 클럭',
+    max_clock: '최대 클럭',
+    mem_type: '메모리 규격',
+    tdp: 'TDP',
+  },
+  gpu: {
+    chipset_manufacturer: '칩셋 제조사',
+    family_type: '제품 시리즈',
+    chipset: '칩셋',
+    vram_type: '메모리 종류',
+    vram_size: '메모리 용량',
+    interface: '인터페이스',
+    max_monitor_count: '모니터 지원',
+    power_consumption: '사용 전력',
+  },
+  ram: {
+    usage_type: '사용 장치',
+    form_factor: '메모리 규격',
+    size: '메모리 용량',
+    generation: '제품 분류',
+    base_clock: '동작 클럭(대역폭)',
+    package_count: '램 개수',
+  },
+  mb: {
+    board_type: '제품 분류',
+    cpu_socket: 'CPU 소켓',
+    cpu_chipset: '세부 칩셋',
+    power_phase: '전원부',
+    ram_type: '메모리 종류',
+    ram_speed: '메모리 속도',
+    ram_slot_count: '메모리 슬롯 수',
+    form_factor: '폼팩터',
+  },
+  ssd: {
+    interface: '인터페이스',
+    size: '용량',
+    form_factor: '폼팩터',
+    nand_type: '메모리 타입',
+    dram_type_size: 'DRAM (유형과 용량)',
+    protocol: '프로토콜',
+  },
+  hdd: {
+    usage_type: '제품 분류',
+    disk_standard_size: '디스크 크기',
+    disk_size: '디스크 용량',
+    interface: '인터페이스',
+    buffer_size: '버퍼 용량',
+    rpm: '회전 수',
+    max_speed: '전송 속도',
+    access_method: '기록방식',
+  },
+};
diff --git a/src/repositories/partRepository.js b/src/repositories/partRepository.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e453fa900d1ff186583ff4448ae11f0a7531d62
--- /dev/null
+++ b/src/repositories/partRepository.js
@@ -0,0 +1,62 @@
+import pool from '../db.js';
+
+const PartRepository = {
+  async findById(id) {
+    const resp = await pool.query(
+      `SELECT type, name, image_url FROM parts WHERE id = $1`,
+      [id]
+    );
+    const [part] = resp.rows;
+    return part;
+  },
+  async findMetaByTypeAndId(type, id) {
+    const resp = await pool.query(
+      `SELECT * FROM part_info_${type} WHERE part_id = $1;`,
+      [id]
+    );
+    const [info] = resp.rows;
+    return info;
+  },
+  async getColumnsByType(type) {
+    const resp = await pool.query(
+      `
+      SELECT column_name 
+      FROM information_schema.columns 
+      WHERE table_name = $1
+      ORDER BY ordinal_position;
+      `,
+      [`part_info_${type}`.toLowerCase()]
+    );
+    return resp.rows.map((row) => row.column_name);
+  },
+
+  async getFilterDataByTypeAndColumn(type, column) {
+    const query = `
+    SELECT DISTINCT p.${column}
+    FROM relations r
+    JOIN part_info_${type} p ON r.part_id = p.part_id -- id 대신 part_id 사용
+    WHERE p.${column} IS NOT NULL
+    ORDER BY p.${column}
+  `;
+    const resp = await pool.query(query);
+    return resp.rows.map((row) => row[column]);
+  },
+
+  async getPartsByFilters(partType, whereClauses, queryValues) {
+    const query = `
+  SELECT 
+    parts.id AS partId, 
+    parts.name, 
+    parts.image_url
+  FROM part_info_${partType.toLowerCase()} 
+  INNER JOIN parts 
+  ON part_info_${partType.toLowerCase()}.part_id = parts.id
+  ${whereClauses ? `WHERE ${whereClauses}` : ''}
+  LIMIT 20;
+`;
+    const result = await pool.query(query, queryValues);
+    return result.rows;
+  },
+};
+
+export default PartRepository;
diff --git a/src/routes/parts.js b/src/routes/parts.js
index 2ea1b2c894799ea8acb89325eaf1158e5c7e43a7..b995962256ae13284f72da104caa89c9ac183a32 100644
--- a/src/routes/parts.js
+++ b/src/routes/parts.js
@@ -1,9 +1,36 @@
 import { Router } from 'express';
+import { wrapAsync } from '../utils.js';
+import { ReportableError } from '../errors.js';
+import PartService from '../services/partService.js';
 
 const partRouter = Router();
 
-partRouter.get('/', (req, res) => {
-  return res.send({ message: 'parts thigns' });
-});
+partRouter.get(
+  '/',
+  wrapAsync(async (req, res) => {
+    const { partType, filters } = req.query;
+    const parts = await PartService.getParts(partType, filters);
+    return res.sendResponse('', 200, { parts });
+  })
+);
+
+partRouter.get(
+  '/filters',
+  wrapAsync(async (req, res) => {
+    const filters = await PartService.getFilters();
+    return res.sendResponse('', 200, filters);
+  })
+);
+
+partRouter.get(
+  '/:id',
+  wrapAsync(async (req, res) => {
+    const { id } = req.params;
+    if (!id) throw ReportableError(400, 'id 를 지정해주세요');
+
+    const part = await PartService.getById(+id, true);
+    return res.sendResponse('', 200, part);
+  })
+);
 
 export default partRouter;
diff --git a/src/services/partService.js b/src/services/partService.js
new file mode 100644
index 0000000000000000000000000000000000000000..0fa797d00b0ffacff33cdbc4a8364ca6643054ed
--- /dev/null
+++ b/src/services/partService.js
@@ -0,0 +1,79 @@
+import { columnMapping } from '../constants/columnMapping.js';
+import { ReportableError } from '../errors.js';
+import PartRepository from '../repositories/partRepository.js';
+
+const PartService = {
+  cleanEntity(entity) {
+    return entity;
+  },
+  async getById(id, detail = false) {
+    if (!id || id <= 0)
+      throw new ReportableError(400, '올바르지 않은 id 입니다.');
+
+    const part = await PartRepository.findById(id);
+    if (!part) throw new ReportableError(404, '해당 부품을 찾을 수 없습니다');
+
+    if (detail) {
+      // eslint-disable-next-line no-unused-vars
+      const { part_id, ...infos } = await PartRepository.findMetaByTypeAndId(
+        part.type.toLowerCase(),
+        id
+      );
+      const description = Object.values(infos).join(' / ');
+      part['description'] = description;
+    }
+
+    return this.cleanEntity(part);
+  },
+  async getFilters() {
+    const types = Object.keys(columnMapping);
+    const filters = {};
+
+    for (const type of types) {
+      const columns = await PartRepository.getColumnsByType(type);
+
+      filters[type.toUpperCase()] = {};
+      for (const column of columns) {
+        const koreanName = columnMapping[type]?.[column];
+        if (koreanName) {
+          const filterValues =
+            await PartRepository.getFilterDataByTypeAndColumn(type, column);
+
+          filters[type.toUpperCase()][koreanName] = {
+            column,
+            values: filterValues,
+          };
+        }
+      }
+    }
+
+    return filters;
+  },
+
+  async getParts(partType, filters) {
+    if (!partType) {
+      throw new ReportableError(400, '파트 타입이 필요합니다.');
+    }
+
+    let parsedFilters;
+    try {
+      parsedFilters = filters ? JSON.parse(filters) : {};
+    } catch {
+      throw new ReportableError(400, '잘못된 JSON 형태입니다.');
+    }
+
+    const whereClauses = Object.entries(parsedFilters)
+      .map(([key], index) => `${key} = $${index + 1}`)
+      .join(' AND ');
+    const queryValues = Object.values(parsedFilters);
+
+    const parts = await PartRepository.getPartsByFilters(
+      partType,
+      whereClauses,
+      queryValues
+    );
+    return parts;
+  },
+};
+
+export default PartService;