chore: 사내 Node 샘플 scaffold (new-project 템플릿)

podman 로컬개발 + Coolify(Dockerfile) 배포용 베이스.
- Express 서버: /healthz, /db, /s3 연결 확인
- src/config.js 단일 진입점 → src/db.js(pg), src/s3.js(MinIO)
- scripts/check-db.js, check-minio.js 연결 점검
- Dockerfile / compose.yaml / .dockerignore, 비밀값은 env 파일(gitignore)에서만

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 08:00:57 +00:00
commit 21ace490e5
15 changed files with 2332 additions and 0 deletions

26
src/config.js Normal file
View File

@@ -0,0 +1,26 @@
// 환경설정 — 모든 외부 연결정보는 환경변수에서만 읽는다(코드에 비밀값 금지).
//
// 값의 출처: 프로젝트 폴더의 .project-env (DB 본인 schema + S3). 프로젝트 폴더에 cd 하면
// 로그인 셸이 자동 export → 여기 process.env 로 그대로 들어온다(별도 dotenv 불필요).
// MinIO(S3): 사내 오브젝트 스토리지. endpoint는 API 호스트(minio.bokdev.in),
// 콘솔(minioc.bokdev.in)이 아님에 주의.
//
// Coolify 배포 시에는 Coolify의 Environment Variables 화면에서 같은 키들을 채운다.
export const config = {
port: parseInt(process.env.PORT || "3000", 10),
// Postgres — .project-env 의 DATABASE_URL. Coolify에선 본인 DB 접속정보를 넣을 것.
databaseUrl: process.env.DATABASE_URL || "",
// MinIO / S3 호환
s3: {
endpoint: process.env.S3_ENDPOINT || "https://minio.bokdev.in",
region: process.env.S3_REGION || "us-east-1",
bucket: process.env.S3_BUCKET || "coolify-user-data",
accessKeyId: process.env.S3_ACCESS_KEY_ID || "",
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || "",
// MinIO는 path-style 필요
forcePathStyle: true,
},
};

15
src/db.js Normal file
View File

@@ -0,0 +1,15 @@
// Postgres 연결 풀. DATABASE_URL 한 줄로 접속(직원 전용 schema가 search_path에 고정됨).
import pg from "pg";
import { config } from "./config.js";
export const pool = new pg.Pool({
connectionString: config.databaseUrl,
max: 5,
});
export async function pingDb() {
const { rows } = await pool.query(
"select current_user as user, current_schema as schema, now() as now"
);
return rows[0];
}

24
src/s3.js Normal file
View File

@@ -0,0 +1,24 @@
// MinIO(S3 호환) 클라이언트. 자격증명은 env에서만 읽는다.
import { S3Client, ListObjectsV2Command, HeadBucketCommand } from "@aws-sdk/client-s3";
import { config } from "./config.js";
export const s3 = new S3Client({
endpoint: config.s3.endpoint,
region: config.s3.region,
forcePathStyle: config.s3.forcePathStyle,
credentials: {
accessKeyId: config.s3.accessKeyId,
secretAccessKey: config.s3.secretAccessKey,
},
});
export async function pingS3() {
await s3.send(new HeadBucketCommand({ Bucket: config.s3.bucket }));
const out = await s3.send(
new ListObjectsV2Command({ Bucket: config.s3.bucket, MaxKeys: 5 })
);
return {
bucket: config.s3.bucket,
sampleKeys: (out.Contents || []).map((o) => o.Key),
};
}

33
src/server.js Normal file
View File

@@ -0,0 +1,33 @@
// 최소 Express 서버 — /healthz, /db, /s3 로 연결 확인.
import express from "express";
import { config } from "./config.js";
import { pingDb } from "./db.js";
import { pingS3 } from "./s3.js";
const app = express();
app.get("/healthz", (_req, res) => res.json({ ok: true }));
app.get("/db", async (_req, res) => {
try {
res.json({ ok: true, ...(await pingDb()) });
} catch (e) {
res.status(500).json({ ok: false, error: String(e.message || e) });
}
});
app.get("/s3", async (_req, res) => {
try {
res.json({ ok: true, ...(await pingS3()) });
} catch (e) {
res.status(500).json({ ok: false, error: String(e.message || e) });
}
});
app.get("/", (_req, res) =>
res.json({ name: "sample", endpoints: ["/healthz", "/db", "/s3"] })
);
app.listen(config.port, "0.0.0.0", () => {
console.log(`sample listening on :${config.port}`);
});