express 설치

    npm install -g express-generator

    express 프로젝트 --view=ejs
    
    cd 프로젝트

    npm install

typescript 설정

    npm install -D @types/cookie-parser @types/cors @types/debug @types/ejs @types/express @types/morgan @types/node nodemon ts-node typescript

    npx tsc --init

tsconfig.json 설정

{
  "compilerOptions": {
    "target": "ES6",                         // TypeScript가 컴파일할 ECMAScript 버전 (이 경우 ES6).
    "module": "commonjs",                    // 컴파일된 코드에서 사용할 모듈 시스템. `commonjs`는 일반적으로 Node.js에서 사용됩니다.
    "strict": true,                          // 모든 엄격한 타입 검사 옵션을 활성화합니다 (예: 암시적인 `any` 사용 방지).
    "esModuleInterop": true,                 // 기본 내보내기(default export)가 없는 모듈에서 기본 임포트를 허용하여 CommonJS 모듈과의 호환성을 높입니다.
    "skipLibCheck": true,                    // 선언 파일(`.d.ts`)의 타입 검사를 생략하여 빌드 성능을 향상시킵니다.
    "forceConsistentCasingInFileNames": true, // 파일 이름의 대소문자가 일관되도록 강제합니다.
    "outDir": "./dist",                      // 컴파일된 JavaScript 파일을 저장할 출력 디렉토리입니다.
    "rootDir": "./src"                       // TypeScript 소스 파일이 위치한 루트 디렉토리입니다.
  },
  "include": ["src/**/*.ts"],                // `src` 디렉토리  하위 디렉토리의 모든 `.ts` 파일을 포함합니다.
  "exclude": ["node_modules"]                // `node_modules` 디렉토리는 컴파일에서 제외됩니다.
}

폴더 구조 변경

    my-app/
    ├── src/
    │   ├── bin/
    │   ├── routes/
    │   ├── controllers/
    │   ├── app.ts (혹은 index.ts)
    │   └── ...
    ├── views/
    │   └── index.ejs
    ├── public/
    │   ├── images/
    │   ├── styles/
    │   └── scripts/
    ├── .env
    ├── package.json
    ├── tsconfig.json
    └── ...

js -> ts 변경

app.ts

//app.ts
import 'dotenv/config'; // dotenv 설정
import createError from 'http-errors';
import express, {Request, Response, NextFunction} from 'express';
import cors from 'cors';
import path from 'path';
import cookieParser from 'cookie-parser';
import logger from 'morgan';

import indexRouter from './routes/index';
import usersRouter from './routes/users';

const app = express();

// view engine setup
app.set('views', path.join(__dirname, '../views'));
app.set('view engine', 'ejs');

app.use(cors());

// 로깅 레벨 설정
const loggingLevel = process.env.LOGGING_LEVEL || 'dev'; // 기본값은 'dev'
app.use(logger(loggingLevel));

// 미들웨어 설정
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, '../public')));

// 라우터 설정
app.use('/', indexRouter);
app.use('/users', usersRouter);

// 404 처리 미들웨어
app.use(function (req: Request, res: Response, next: NextFunction) {
    next(createError(404));
});

// 에러 처리 미들웨어
app.use(function (err: any, req: Request, res: Response, next: NextFunction) {
    // 로컬 변수 설정 (개발 환경에서만 상세 에러 제공)
    res.locals.message = err.message;
    res.locals.error = req.app.get('env') === 'development' ? err : {};

    // 에러 페이지 렌더링
    res.status(err.status || 500);
    res.render('error');
});

export default app;

/bin/www

#!/usr/bin/env node

import dotenv from 'dotenv';
import createError from 'http-errors';
import debug from 'debug';
import http from 'http';
import app from '../app';

const env: string = process.env.NODE_ENV || 'development';
dotenv.config({ path: `./.env.${env}` });
console.log(`Running in ${env} mode`);

// 포트 설정
const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

// HTTP 서버 생성
const server = http.createServer(app);

// 서버 실행
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

// 포트를 정상화하는 함수
function normalizePort(val: string): number | string | boolean {
  const port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

// HTTP 서버 에러 처리
function onError(error: any): void {
  if (error.syscall !== 'listen') {
    throw error;
  }

  const bind = typeof port === 'string'
      ? `Pipe ${port}`
      : `Port ${port}`;

  // 특정 listen 에러 처리
  switch (error.code) {
    case 'EACCES':
      console.error(`${bind} requires elevated privileges`);
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(`${bind} is already in use`);
      process.exit(1);
      break;
    default:
      throw error;
  }
}

// HTTP 서버가 리스닝 중일 때
function onListening() {
  const addr = server.address();
  if (addr) {
    const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
    debug('Listening on ' + bind);
  }
}

/routes/index

import express, { Request, Response, NextFunction } from 'express';
const router = express.Router();

/* GET home page. */
router.get('/', (req: Request, res: Response, next: NextFunction) => {
  res.render('index', { title: 'Express' });
});

export default router;