다음을 통해 공유


Azure SQL 데이터베이스의 데이터베이스에 연결하고 Node.js 및 mssql npm 패키지를 사용하여 데이터를 쿼리하는 방법

적용 대상: Azure SQL 데이터베이스

이 빠른 시작에서는 Azure SQL 데이터베이스의 데이터베이스에 애플리케이션을 연결하고 Node.js 및 mssql을 사용하여 쿼리를 수행하는 방법을 설명합니다. 이 빠른 시작은 데이터베이스에 연결하는 데 권장되는 암호 없는 방법을 사용합니다.

개발자용 암호 없는 연결

암호 없는 연결은 보다 안전한 Azure 리소스에 액세스 메커니즘을 제공합니다. 이 문서에서는 다음 고급 단계를 통해 암호 없는 연결을 사용하여 Azure SQL 데이터베이스에 연결합니다.

  • 암호 없는 인증을 위한 환경을 준비합니다.
    • 로컬 환경의 경우 개인 ID가 사용됩니다. 이 ID는 IDE, CLI 또는 기타 로컬 개발 도구에서 가져올 수 있습니다.
    • 클라우드 환경의 경우 관리 ID 가 사용됩니다.
  • Azure ID 라이브러리의 DefaultAzureCredential(을)를 사용하여 인증된 자격 증명을 가져와 환경에서 인증을 수행합니다.
  • 인증된 자격 증명을 사용하여 리소스 액세스의 Azure SDK 클라이언트 개체를 만듭니다.

암호 없는 허브의 암호 없는 연결에 대해 자세히 알아 보세요.

필수 조건

데이터베이스 서버 구성

Azure SQL 데이터베이스에 대한 암호 없는 보안 연결에는 특정 데이터베이스 구성이 필요합니다. 로컬 및 호스팅된 환경 모두에서 Azure SQL 데이터베이스에 제대로 연결하려면 Azure의 논리 서버에서 다음 설정을 확인합니다.

  1. 로컬 개발 연결의 경우 논리 서버가 로컬 컴퓨터 IP 주소 및 기타 Azure 서비스에 연결할 수 있도록 구성되어 있는지 확인합니다.

    • 서버의 네트워킹 페이지로 이동합니다.

    • 선택한 네트워크 라디오 버튼을 눌러 추가 구성 옵션을 표시합니다.

    • 클라이언트 IPv4 주소 추가(xx.xx.xx.xx.xx)를 선택하여 로컬 컴퓨터 IPv4 주소에서 연결을 활성화하는 방화벽 규칙을 추가합니다. 또는 + 방화벽 규칙 추가를 선택하여 원하는 특정 IP 주소를 입력할 수도 있습니다.

    • Azure 서비스 및 리소스의 서버 액세스 허용 확인란을 선택합니다.

      방화벽 규칙을 구성하는 방법을 보여 주는 스크린샷.

      Warning

      프로덕션 시나리오에는 Azure 서비스 및 리소스의 서버 액세스 허용을 보안상 권장하지 않습니다. 실제 애플리케이션에는 더 강력한 방화벽 제한 또는 가상 네트워크 구성과 같은 보다 강력한 보안 접근 방식을 적용해야 합니다.

      다음 리소스에서 데이터베이스 보안 구성에 대해 자세히 알아 보세요.

  2. 또한 서버에는 Microsoft Entra 인증이 활성화 되어 있어야 하며 Microsoft Entra 관리자 계정이 할당되어 있어야 합니다. 로컬 개발 연결의 경우, Microsoft Entra 관리자 계정은 로컬로 Visual Studio 또는 Azure CLI에 로그인할 수도 있는 계정이어야 합니다. 서버의 Microsoft Entra 인증이 활성화 여부는 논리 서버의 Microsoft Entra ID 페이지에서 확인할 수 있습니다.

    Microsoft Entra 인증을 활성화하는 방법을 보여 주는 스크린샷.

  3. 개인 Azure 계정을 사용하는 경우, 계정을 서버 관리자로 할당하기 위해 Azure SQL 데이터베이스에 Microsoft Entra를 설정 및 구성했는지 확인해야 합니다. 회사 계정을 사용하는 경우, Microsoft Entra ID가 이미 구성되어 있을 수 있습니다.

프로젝트를 만듭니다.

이 섹션의 단계에서는 Node.js REST API를 만듭니다.

  1. 프로젝트를 위한 새 디렉터리를 만들어 이동합니다.

  2. 터미널에서 다음 명령을 실행하여 프로젝트를 초기화합니다.

    npm init -y
    
  3. 이 문서의 샘플 코드에 사용되는 필수 패키지를 설치합니다.

    npm install mssql express swagger-ui-express yamljs dotenv
    
  4. Visual Studio Code에서 프로젝트를 엽니다.

    code .
    
  5. package.json 파일을 열고 이름 속성 다음에 다음 속성과 값을 추가하여 ESM 모듈에 대한 프로젝트를 구성합니다.

    "type": "module",
    

Express.js 애플리케이션 코드 만들기

Express.js OpenAPI 애플리케이션을 만들려면 다음 파일을 만듭니다.

파일 설명
.env.development 로컬 개발 전용 환경 파일.
index.js 포트 3000에서 Express.js 앱을 시작하는 기본 애플리케이션 파일.
person.js CRUD 작업을 처리하는 Express.js /person route API 파일.
openapi.js OpenAPI 탐색기 UI Express.js /api-docs 경로 루트는 이 경로로 리디렉션됩니다.
openApiSchema.yml Person API를 정의하는 OpenAPI 3.0 스키마 파일.
config.js 환경 변수를 읽고 적절한 mssql 연결 개체를 생성하는 구성 파일.
database.js mssql npm 패키지를 사용하여 Azure SQL CRUD 작업을 처리하는 데이터베이스 클래스.
.vscode\settings.json 배포하는 동안 glob 패턴별로 파일을 무시합니다.
  1. index.js 파일을 만들고 다음 코드를 추가합니다.

    import express from 'express';
    
    // Import App routes
    import person from './person.js';
    import openapi from './openapi.js';
    
    const port = process.env.PORT || 3000;
    
    const app = express();
    
    // Connect App routes
    app.use('/api-docs', openapi);
    app.use('/persons', person);
    app.use('*', (_, res) => {
      res.redirect('/api-docs');
    });
    
    // Start the server
    app.listen(port, () => {
      console.log(`Server started on port ${port}`);
    });
    
  2. person.js 경로 파일을 만들고 다음 코드를 추가합니다.

    import express from 'express';
    import { 
      passwordConfig as SQLAuthentication, 
      noPasswordConfig as PasswordlessConfig 
    } from './config.js';
    import { createDatabaseConnection } from './database.js';
    
    const router = express.Router();
    router.use(express.json());
    
    const database = await createDatabaseConnection(SQLAuthentication);
    
    router.get('/', async (req, res) => {
      try {
        // Return a list of persons
    
        const persons = await database.readAll();
        console.log(`persons: ${JSON.stringify(persons)}`);
        res.status(200).json(persons);
      } catch (err) {
        res.status(500).json({ error: err?.message });
      }
    });
    
    router.post('/', async (req, res) => {
      try {
        // add a person
        const person = req.body;
        console.log(`person: ${JSON.stringify(person)}`);
        const rowsAffected = await database.create(person);
        res.status(201).json({ rowsAffected });
      } catch (err) {
        res.status(500).json({ error: err?.message });
      }
    });
    
    router.get('/:id', async (req, res) => {
      try {
        // Get the person with the specified ID
        const personId = req.params.id;
        console.log(`personId: ${personId}`);
        if (personId) {
          const result = await database.read(personId);
          console.log(`persons: ${JSON.stringify(result)}`);
          res.status(200).json(result);
        } else {
          res.status(404);
        }
      } catch (err) {
        res.status(500).json({ error: err?.message });
      }
    });
    
    router.put('/:id', async (req, res) => {
      try {
        // Update the person with the specified ID
        const personId = req.params.id;
        console.log(`personId: ${personId}`);
        const person = req.body;
    
        if (personId && person) {
          delete person.id;
          console.log(`person: ${JSON.stringify(person)}`);
          const rowsAffected = await database.update(personId, person);
          res.status(200).json({ rowsAffected });
        } else {
          res.status(404);
        }
      } catch (err) {
        res.status(500).json({ error: err?.message });
      }
    });
    
    router.delete('/:id', async (req, res) => {
      try {
        // Delete the person with the specified ID
        const personId = req.params.id;
        console.log(`personId: ${personId}`);
    
        if (!personId) {
          res.status(404);
        } else {
          const rowsAffected = await database.delete(personId);
          res.status(204).json({ rowsAffected });
        }
      } catch (err) {
        res.status(500).json({ error: err?.message });
      }
    });
    
    export default router;
    

    암호 없는 인증의 경우 createDatabaseConnection에 전달되는 매개 변수를 SQLAuthentication에서 PasswordlessConfig으로 변경합니다.

    const database = await createDatabaseConnection(PasswordlessConfig);
    
  3. openapi.js 경로 파일을 만들고 OpenAPI UI 탐색기에 대해 다음 코드를 추가합니다.

    import express from 'express';
    import { join, dirname } from 'path';
    import swaggerUi from 'swagger-ui-express';
    import yaml from 'yamljs';
    import { fileURLToPath } from 'url';
    
    const __dirname = dirname(fileURLToPath(import.meta.url));
    
    const router = express.Router();
    router.use(express.json());
    
    const pathToSpec = join(__dirname, './openApiSchema.yml');
    const openApiSpec = yaml.load(pathToSpec);
    
    router.use('/', swaggerUi.serve, swaggerUi.setup(openApiSpec));
    
    export default router;
    

mssql 연결 개체 구성

mssql 패키지는 인증 유형에 대한 구성 설정을 제공하여 Azure SQL 데이터베이스에 대한 연결을 구현합니다.

  1. Visual Studio Code에서 config.js 파일을 만들고 다음 mssql 구성 코드를 추가하여 Azure SQL 데이터베이스에 인증합니다.

    import * as dotenv from 'dotenv';
    
    if(process.env.NODE_ENV === 'development') {
      dotenv.config({ path: `.env.${process.env.NODE_ENV}`, debug: true });
    }
    
    // TIP: Port must be a number, not a string!
    const server = process.env.AZURE_SQL_SERVER;
    const database = process.env.AZURE_SQL_DATABASE;
    const port = +process.env.AZURE_SQL_PORT;
    const type = process.env.AZURE_SQL_AUTHENTICATIONTYPE;
    const user = process.env.AZURE_SQL_USER;
    const password = process.env.AZURE_SQL_PASSWORD;
    
    export const noPasswordConfig = {
      server,
      port,
      database,
      authentication: {
        type
      },
      options: {
        encrypt: true
      }
    };
    
    export const passwordConfig = {
      server,
      port,
      database,
      user,
      password,
      options: {
        encrypt: true
      }
    };
    

로컬 환경 변수 파일 만들기

로컬 환경 변수에 대한 .env.development 파일 만들기

다음 텍스트를 추가하고 <YOURSERVERNAME><YOURDATABASENAME>에 대한 값으로 업데이트합니다.

AZURE_SQL_SERVER=<YOURSERVERNAME>.database.windows.net
AZURE_SQL_DATABASE=<YOURDATABASENAME>
AZURE_SQL_PORT=1433
AZURE_SQL_AUTHENTICATIONTYPE=azure-active-directory-default

참고 항목

암호 없는 구성 개체체는 사용자 이름, 암호 또는 액세스 키와 같은 기밀사항을 포함하지 않으므로 소스 제어에 커밋해도 안전합니다.

Azure SQL Server Database에 연결하려면 코드를 추가하세요.

  1. database.js라는 파일을 만들고 다음 코드를 추가합니다.

    import sql from 'mssql';
    
    let database = null;
    
    export default class Database {
      config = {};
      poolconnection = null;
      connected = false;
    
      constructor(config) {
        this.config = config;
      }
    
      async connect() {
        try {
          this.poolconnection = await sql.connect(this.config);
          this.connected = true;
          console.log('Database connected successfully.');
          return this.poolconnection;
        } catch (error) {
          console.error('Error connecting to the database:', error);
          this.connected = false;
        }
      }
    
      async disconnect() {
        try {
          if (this.connected) {
            await this.poolconnection.close();
            this.connected = false;
            console.log('Database disconnected successfully.');
          }
        } catch (error) {
          console.error('Error disconnecting from the database:', error);
        }
      }
    
      async executeQuery(query) {
        const request = this.poolconnection.request();
        const result = await request.query(query);
    
        return result.rowsAffected[0];
      }
    
      async create(data) {
        const request = this.poolconnection.request();
    
        request.input('firstName', sql.NVarChar(255), data.firstName);
        request.input('lastName', sql.NVarChar(255), data.lastName);
    
        const result = await request.query(
          `INSERT INTO Person (firstName, lastName) VALUES (@firstName, @lastName)`
        );
    
        return result.rowsAffected[0];
      }
    
      async readAll() {
        const request = this.poolconnection.request();
        const result = await request.query(`SELECT * FROM Person`);
    
        return result.recordsets[0];
      }
    
      async read(id) {
        const request = this.poolconnection.request();
        const result = await request
          .input('id', sql.Int, +id)
          .query(`SELECT * FROM Person WHERE id = @id`);
    
        return result.recordset[0];
      }
    
      async update(id, data) {
        const request = this.poolconnection.request();
    
        request.input('id', sql.Int, +id);
        request.input('firstName', sql.NVarChar(255), data.firstName);
        request.input('lastName', sql.NVarChar(255), data.lastName);
    
        const result = await request.query(
          `UPDATE Person SET firstName=@firstName, lastName=@lastName WHERE id = @id`
        );
    
        return result.rowsAffected[0];
      }
    
      async delete(id) {
        const idAsNumber = Number(id);
    
        const request = this.poolconnection.request();
        const result = await request
          .input('id', sql.Int, idAsNumber)
          .query(`DELETE FROM Person WHERE id = @id`);
    
        return result.rowsAffected[0];
      }
    
      async createTable() {
        if (process.env.NODE_ENV === 'development') {
          this.executeQuery(
            `IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Person')
             BEGIN
               CREATE TABLE Person (
                 id int NOT NULL IDENTITY, 
                 firstName varchar(255), 
                 lastName varchar(255)
               );
             END`
          )
            .then(() => {
              console.log('Table created');
            })
            .catch((err) => {
              // Table may already exist
              console.error(`Error creating table: ${err}`);
            });
        }
      }
    }
    
    export const createDatabaseConnection = async (passwordConfig) => {
      database = new Database(passwordConfig);
      await database.connect();
      await database.createTable();
      return database;
    };
    

로컬로 앱 테스트

이제 앱을 로컬로 테스트할 준비가 완료되었습니다. 데이터베이스의 관리자로 설정한 계정과 동일한 계정으로 Visual Studio Code의 Azure Cloud에 로그인했는지 확인합니다.

  1. 다음 명령을 사용하여 애플리케이션을 실행합니다. 앱은 포트 3000에서 시작합니다.

    NODE_ENV=development node index.js
    

    사람 테이블은 이 애플리케이션을 실행할 때 데이터베이스에 만들어집니다.

  2. 브라우저에서 http://localhost:3000의 OpenAPI 탐색기로 이동합니다.

  3. Swagger UI 페이지에서 POST 메서드를 확장하고 시도를 선택합니다.

  4. 속성에 대한 값을 포함하도록 샘플 JSON을 수정합니다. ID 속성은 무시됩니다.

    API를 테스트하는 방법을 보여주는 스크린샷.

  5. 실행을 선택하여 데이터베이스에 새 기록을 추가합니다. API는 성공적인 응답을 반환합니다.

  6. Swagger UI 페이지에서 GET 메서드를 확장하고 시도를 선택합니다. 실행을 선택하면 방금 만든 사람이 반환됩니다.

zip 배포할 프로젝트 구성

  1. .vscode 폴더를 만들고 폴더에 settings.json 파일을 만듭니다.

  2. zip 배포 중에 환경 변수 및 종속성을 무시하려면 다음을 추가합니다.

    {
        "appService.zipIgnorePattern": ["./.env*","node_modules{,/**}"]
    }
    

Azure App Service에 배포

Azure에 앱을 배포할 준비가 완료되었습니다. Visual Studio Code는 Azure App Service를 만들고 단일 워크플로에 애플리케이션을 배포할 수 있습니다.

  1. 앱이 중지되었는지 확인합니다.

  2. 아직 Azure에 로그인하지 않은 경우 명령 팔레트에서 Azure: Azure Cloud에 로그인 명령을 선택합니다(Ctrl + Shift + P).

  3. Visual Studio Code의 Azure 탐색기 창에서 App Services 노드를 마우스 오른쪽 단추로 클릭하고 새 웹앱 만들기(고급)를 선택합니다.

  4. 다음 단계를 따라 App Service 인스턴스를 만듭니다.

    prompt
    새 웹앱에 대한 전역 고유 이름을 입력합니다. azure-sql-passwordless(와)과 같은 프롬프트를 입력합니다. 123(와)과 같은 고유 문자열을 뒤에 추가합니다.
    새 리소스에 대한 리소스 그룹 선택 +새 리소스 그룹 만들기를 선택한 다음 기본 이름을 선택합니다.
    런타임 스택을 선택하세요. Node.js 스택의 LTS 버전을 선택합니다.
    OS를 선택합니다. Linux를 선택합니다.
    새 리소스의 위치 선택 가까운 위치를 선택합니다.
    Azure App Service(Linux)를 선택합니다. 새 App Service 요금제 만들기를 선택한 다음 기본 이름을 선택합니다.
    가격 책정 계층을 선택합니다. 무료(F1)를 선택합니다.
    앱의 Application Insights 리소스를 선택합니다. 지금은 건너뛰기를 선택합니다.
  5. 계속하기 전에 앱이 생성되었다는 알림이 표시될 때까지 기다립니다.

  6. Azure 탐색기에서 App Services 노드를 확장하고 새 앱을 마우스 오른쪽 단추로 클릭합니다.

  7. 웹 앱에 배포를 선택합니다.

    웹앱에 배포가 강조 표시된 Azure 탐색기의 Visual Studio Code 스크린샷.

  8. JavaScript 프로젝트의 루트 폴더를 선택합니다.

  9. Visual Studio Code 팝업이 뜨면 배포를 선택합니다.

배포가 완료되도 앱은 Azure에서 제대로 작동하지 않습니다. 데이터를 가져오려면 App Service와 SQL 데이터베이스 간의 보안 연결을 구성해야 합니다.

Azure SQL Server Database에 App Service 연결

App Service 인스턴스와 Azure SQL 데이터베이스 간에 암호 없는 연결을 만들려면 다음 단계를 진행해야 합니다.

  1. App Service 관리 ID 만들기
  2. SQL 데이터베이스 사용자를 만들고 App Service 관리 ID와 연결합니다.
  3. 읽기, 쓰기 및 기타 작업을 허용하도록 데이터베이스 사용자에게 SQL 역할을 할당합니다.

다음 단계에 사용할 수 있는 여러 도구가 있습니다.

서비스 커넥터는 Azure의 여러 서비스 간에 인증된 연결을 간소화하는 도구입니다. 서비스 커넥터는 현재 az webapp connection create sql 명령을 사용하여 Azure CLI를 통해 App Service의 Azure SQL 데이터베이스 연결을 지원합니다. 이 단일 명령은 위에서 언급된 세 단계를 완료합니다.

서비스 커넥터로 관리 ID 만들기

Azure 포털 Cloud Shell에서 다음 명령을 실행합니다. Cloud Shell의 Azure CLI가 최신 버전입니다. <>의 값을 사용자 값으로 바꿉니다.

az webapp connection create sql \
    -g <app-service-resource-group> \
    -n <app-service-name> \
    --tg <database-server-resource-group> \
    --server <database-server-name> \
    --database <database-name> \
    --system-identity

App Service 앱 설정 확인

App Service 설정에서 서비스 커넥터가 변경한 내용을 확인할 수 있습니다.

  1. Visual Studio Code의 Azure 탐색기에서 App Service를 마우스 오른쪽 단추로 클릭하고 포털에서 열기를 선택합니다.

  2. App Service의 ID 페이지로 이동합니다. 시스템 할당 탭에서 상태켜짐 상태인지 확인합니다. 이 값은 앱에서 시스템 할당 관리 ID를 사용할 수 있음을 의미합니다.

  3. App Service의 구성 페이지로 이동합니다. 애플리케이션 설정 탭에는 mssql 구성 개체에 이미 있는 여러 환경 변수가 표시됩니다.

    • AZURE_SQL_SERVER
    • AZURE_SQL_DATABASE
    • AZURE_SQL_PORT
    • AZURE_SQL_AUTHENTICATIONTYPE

    속성 이름 또는 값을 삭제하거나 변경하지 마세요.

배포된 애플리케이션 테스트

앱 URL로 이동하여 Azure SQL 데이터베이스 연결이 작동하는지 테스트합니다. 앱 URL는 App Service 개요 페이지에서 찾을 수 있습니다.

로컬로 만든 사람이 브라우저에 표시되어야 합니다. 축하합니다! 이제 애플리케이션이 로컬 및 호스팅된 환경 모두에서 Azure SQL 데이터베이스에 연결되었습니다.

테스트 중 500 Internal Server 오류가 발생하는 경우 데이터베이스 네트워킹 구성 때문일 수 있습니다. 논리 서버가 데이터베이스 구성 섹션 내용대로 구성되었는지 확인합니다.

리소스 정리

Azure SQL 데이터베이스 작업을 마치면 리소스를 삭제하여 의도하지 않은 비용을 방지하세요.

  1. Azure Portal 검색 창에서 Azure SQL을 입력하고 일치하는 결과를 선택합니다.

  2. 데이터베이스 목록에서 데이터베이스를 찾아 선택합니다.

  3. Azure SQL 데이터베이스 개요 페이지에서 삭제를 선택합니다.

  4. Azure에서 삭제하시겠습니까? 페이지가 열리면확인을 위해 데이터베이스의 이름을 입력한 다음 삭제를 선택합니다.

샘플 코드

이 애플리케이션의 샘플 코드를 사용할 수 있습니다.

다음 단계