Kết nối nodejs với sql server

Tôi có niềm đam mê với cơ sở dữ liệu quan hệ, đặc biệt là máy chủ SQL. Trong suốt sự nghiệp của mình, tôi đã bị cuốn hút vào các khía cạnh khác nhau của cơ sở dữ liệu, chẳng hạn như thiết kế, triển khai, di chuyển, tạo các thủ tục, trình kích hoạt và chế độ xem được lưu trữ một cách cẩn thận. 

Gần đây tôi đã bắt đầu xây dựng ứng dụng Node.js với SQL Server. Hôm nay, tôi sẽ chỉ cho bạn cách thực hiện trong hướng dẫn từng bước này bằng cách tạo một ứng dụng lịch đơn giản. 

Thiết lập môi trường phát triển Node.js của bạn

Trước khi bắt đầu, bạn sẽ cần một số thứ:

  • Node.js  phiên bản 8.0 trở lên.
  • Truy cập vào  SQL Server  phiên bản 2012 trở lên.

Nếu bạn chưa có phiên bản SQL Server có thể kết nối, bạn có thể cài đặt một phiên bản cục bộ để phát triển và thử nghiệm.

Cài đặt SQL Server trên Windows

Tải xuống và cài đặt  SQL Server Developer Edition .

Cài đặt SQL Server trên Mac hoặc Linux

  1. Cài đặt  Docker
  2. Chạy phần sau trong một thiết bị đầu cuối. Thao tác này sẽ tải xuống phiên bản SQL Server 2017 mới nhất dành cho Linux và tạo một vùng chứa mới có tên  sqlserver.
docker pull microsoft/mssql-server-linux:2017-latest
docker run -d --name sqlserver -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=P@55w0rd' -e 'MSSQL_PID=Developer' -p 1433:1433 microsoft/mssql-server-linux:2017-latest

Lưu ý: Để biết thêm thông tin về cách chạy SQL Server cho Linux, hãy xem  SQL Server đang chạy trên máy Mac ?!

Thiết lập cơ sở dữ liệu SQL

Bạn sẽ cần một cơ sở dữ liệu SQL cho hướng dẫn này. Nếu bạn đang chạy SQL Server cục bộ và chưa có cơ sở dữ liệu, bạn có thể tạo một cơ sở dữ liệu bằng tập lệnh sau.

Lưu ý: Nếu bạn có Visual Studio Code, bạn có thể sử dụng phần mở rộng mssql tuyệt vời   để chạy các tập lệnh SQL. Hoặc, bạn có thể sử dụng một ứng dụng như  Azure Data Studio .

USE master;
GO

CREATE DATABASE calendar; -- change this to whatever database name you desire
GO

Tiếp theo, tạo một bảng mới có tên  events. Đây là bảng bạn sẽ sử dụng để lưu trữ các sự kiện lịch.

-- Dropping events table...
DROP TABLE IF EXISTS events;

-- Create events table...
CREATE TABLE events [
   id int IDENTITY[1, 1] PRIMARY KEY CLUSTERED NOT NULL
   , userId nvarchar[50] NOT NULL
   , title nvarchar[200] NOT NULL
   , description nvarchar[1000] NULL
   , startDate date NOT NULL
   , startTime time[0] NULL
   , endDate date NULL
   , endTime time[0] NULL
   , INDEX idx_events_userId [ userId ]
];

Tạo ứng dụng web Node.js

Với Node.js, bạn có thể chọn từ nhiều khuôn khổ khác nhau để tạo các ứng dụng web. Trong hướng dẫn này, bạn sẽ sử dụng  hapi , yêu thích của cá nhân tôi. Ban đầu được tạo ra bởi các kỹ sư của Walmart, nó phù hợp để xây dựng các API, dịch vụ và các ứng dụng web hoàn chỉnh.

Mở dấu nhắc lệnh [Windows] hoặc thiết bị đầu cuối [Mac hoặc Linux] và thay đổi thư mục hiện tại thành nơi bạn muốn tạo dự án của mình. Tạo một thư mục cho dự án của bạn và thay đổi thành thư mục mới.

mkdir node-sql-tutorial
cd node-sql-tutorial

Một  package.json tập tin được yêu cầu cho các dự án Node.js và bao gồm những thứ như thông tin dự án, kịch bản, và phụ thuộc. Sử dụng  npm lệnh để tạo  package.json tệp trong thư mục dự án.

npm init -y

Tiếp theo, cài đặt  hapi dưới dạng phụ thuộc.

npm install hapi@18

Bây giờ, hãy mở dự án trong trình soạn thảo mà bạn chọn.

Nếu bạn chưa có trình soạn thảo mã yêu thích, tôi khuyên bạn nên cài đặt  Visual Studio Code . VS Code có hỗ trợ đặc biệt cho JavaScript và Node.js, chẳng hạn như hoàn thành và gỡ lỗi mã thông minh. Ngoài ra còn có một thư viện lớn các tiện ích mở rộng miễn phí do cộng đồng đóng góp.

Cấu trúc dự án Node.js

Hầu hết các ví dụ “chào thế giới” về ứng dụng Node.js bắt đầu với mọi thứ trong một tệp JavaScript duy nhất. Tuy nhiên, điều cần thiết là phải thiết lập một cấu trúc dự án tốt để hỗ trợ ứng dụng của bạn khi nó phát triển.

Có vô số ý kiến ​​về cách bạn có thể tổ chức một dự án Node.js. Trong hướng dẫn này, cấu trúc dự án cuối cùng sẽ tương tự như sau.

├── package.json
├── client
├── src
│   ├── data
│   ├── plugins
│   ├── routes
│   └── views
└── test

Tạo một máy chủ cơ bản với các tuyến đường

Tạo một thư mục có tên  src. Trong thư mục này, thêm một tệp mới có tên  index.js. Mở tệp và thêm JavaScript sau.

"use strict";

const server = require[ "./server" ];

const startServer = async [] => {
   try {
       // todo: move configuration to separate config
       const config = {
           host: "localhost",
           port: 8080
       };

       // create an instance of the server application
       const app = await server[ config ];

       // start the web server
       await app.start[];

       console.log[ `Server running at //${ config.host }:${ config.port }...` ];
   } catch [ err ] {
       console.log[ "startup error:", err ];
   }
};

startServer[];

Tạo một tệp mới dưới  src tên  server.js. Mở tệp và thêm mã sau. 

"use strict";

const Hapi = require[ "hapi" ];
const routes = require[ "./routes" ];

const app = async config => {
   const { host, port } = config;

   // create an instance of hapi
   const server = Hapi.server[ { host, port } ];

   // store the config for later use
   server.app.config = config;

   // register routes
   await routes.register[ server ];

   return server;
};

module.exports = app;

Tách cấu hình máy chủ khỏi khởi động ứng dụng sẽ giúp việc kiểm tra ứng dụng dễ dàng hơn.

Tiếp theo, tạo một thư mục dưới  src tên  routes. Trong thư mục này, thêm một tệp mới có tên  index.js. Mở tệp và thêm mã sau. 

"use strict";

module.exports.register = async server => {
   server.route[ {
       method: "GET",
       path: "/",
       handler: async [ request, h ] => {
           return "My first hapi server!";
       }
   } ];
};

Cuối cùng, chỉnh sửa  package.json tệp và thay đổi "main" giá trị thuộc  tính thành  "src/index.js". Thuộc tính này hướng dẫn Node.js thực thi tệp nào khi ứng dụng khởi động.

 "main": "src/index.js"

Bây giờ, bạn có thể khởi động ứng dụng. Quay lại cửa sổ lệnh / terminal của bạn và nhập lệnh sau.

node .

Bạn sẽ thấy thông báo  Server running at //localhost:8080.... Mở trình duyệt của bạn và điều hướng đến  //localhost:8080. Trình duyệt của bạn sẽ hiển thị một cái gì đó như sau.

Logout

  • Login
  • Cập nhật các tuyến để hỗ trợ chế độ xem và xác thực

    Bên dưới  src/routes, thêm một tệp mới có tên  auth.js. Thêm mã sau vào tệp này.

    "use strict";
    
    const boom = require[ "boom" ];
    
    module.exports.register = async server => {
       // login route
       server.route[ {
           method: "GET",
           path: "/login",
           options: {
               auth: "session",
               handler: async request => {
                   return `Hello, ${ request.auth.credentials.profile.email }!`;
               }
           }
       } ];
    
       // OIDC callback
       server.route[ {
           method: "GET",
           path: "/authorization-code/callback",
           options: {
               auth: "okta",
               handler: [ request, h ] => {
                   if [ !request.auth.isAuthenticated ] {
                       throw boom.unauthorized[ `Authentication failed: ${ request.auth.error.message }` ];
                   }
                   request.cookieAuth.set[ request.auth.credentials ];
                   return h.redirect[ "/" ];
               }
           }
       } ];
    
       // Logout
       server.route[ {
           method: "GET",
           path: "/logout",
           options: {
               auth: {
                   strategy: "session",
                   mode: "try"
               },
               handler: [ request, h ] => {
                   try {
                       if [ request.auth.isAuthenticated ] {
                           // const idToken = encodeURI[ request.auth.credentials.token ];
    
                           // clear the local session
                           request.cookieAuth.clear[];
                           // redirect to the Okta logout to completely clear the session
                           // const oktaLogout = `${ process.env.OKTA_ORG_URL }/oauth2/default/v1/logout?id_token_hint=${ idToken }&post_logout_redirect_uri=${ process.env.HOST_URL }`;
                           // return h.redirect[ oktaLogout ];
                       }
    
                       return h.redirect[ "/" ];
                   } catch [ err ] {
                       request.log[ [ "error", "logout" ], err ];
                   }
               }
           }
       } ];
    };

    Bây giờ, hãy chỉnh sửa  src/routes/index.js để thay đổi trang chủ để nó hiển thị chế độ xem EJS mới.

    "use strict";
    
    const api = require[ "./api" ];
    const auth = require[ "./auth" ];
    
    module.exports.register = async server => {
       // register api routes
       await api.register[ server ];
    
       // register authentication routes
       await auth.register[ server ];
    
       // home page route
       server.route[ {
           method: "GET",
           path: "/",
           config: {
               auth: {
                   strategy: "session",
                   mode: "optional"
               }
           },
           handler: async [ request, h ] => {
               try {
                   const message = request.auth.isAuthenticated ? `Hello, ${ request.auth.credentials.profile.firstName }!` : "My first hapi server!";
                   return h.view[ "index", {
                       title: "Home",
                       message,
                       isAuthenticated: request.auth.isAuthenticated
                   } ];
               } catch [ err ] {
                   server.log[ [ "error", "home" ], err ];
               }
           }
       } ];
    
       // Serve static files in the /dist folder
       server.route[ {
           method: "GET",
           path: "/{param*}",
           handler: {
               directory: {
                   path: "dist"
               }
           }
       } ];
    };

    Cập nhật các tuyến API và thêm truy vấn SQL

    Bạn cần cập nhật API ứng dụng để truy vấn cơ sở dữ liệu dựa trên người dùng hiện đang đăng nhập. Ở mức tối thiểu, bạn cần các tuyến để tạo, cập nhật và xóa các sự kiện, cùng với các truy vấn SQL tương ứng của chúng.

    Tạo một tệp mới dưới  src/data/events tên  addEvent.sql. Thêm SQL sau vào tệp này.

    INSERT INTO [dbo].[events]
    [
       [userId]
       , [title]
       , [description]
       , [startDate]
       , [startTime]
       , [endDate]
       , [endTime]
    ]
    VALUES
    [
       @userId
       , @title
       , @description
       , @startDate
       , @startTime
       , @endDate
       , @endTime
    ];
    
    SELECT SCOPE_IDENTITY[] AS id;

    Tạo một tệp mới dưới  src/data/events tên  updateEvent.sql. Thêm SQL sau vào tệp này.

    UPDATE  [dbo].[events]
    SET     [title] = @title
           , [description] = @description
           , [startDate] = startDate
           , [startTime] = @startTime
           , [endDate] = @endDate
           , [endTime] = @endTime
    WHERE   [id] = @id
     AND   [userId] = @userId;
    
    SELECT  [id]
           , [title]
           , [description]
           , [startDate]
           , [startTime]
           , [endDate]
           , [endTime]
    FROM    [dbo].[events]
    WHERE   [id] = @id
     AND   [userId] = @userId;

    Tạo một tệp mới dưới  src/data/events tên  deleteEvent.sql. Thêm SQL sau vào tệp này.

    DELETE  [dbo].[events]
    WHERE   [id] = @id
     AND   [userId] = @userId;

    Cập nhật  src/data/events/index.js để chứa mã sau.

    "use strict";
    
    const utils = require[ "../utils" ];
    
    const register = async [ { sql, getConnection } ] => {
       // read in all the .sql files for this folder
       const sqlQueries = await utils.loadSqlQueries[ "events" ];
    
       const getEvents = async userId => {
           // get a connection to SQL Server
           const cnx = await getConnection[];
    
           // create a new request
           const request = await cnx.request[];
    
           // configure sql query parameters
           request.input[ "userId", sql.VarChar[ 50 ], userId ];
    
           // return the executed query
           return request.query[ sqlQueries.getEvents ];
       };
    
       const addEvent = async [ { userId, title, description, startDate, startTime, endDate, endTime } ] => {
           const pool = await getConnection[];
           const request = await pool.request[];
           request.input[ "userId", sql.VarChar[ 50 ], userId ];
           request.input[ "title", sql.NVarChar[ 200 ], title ];
           request.input[ "description", sql.NVarChar[ 1000 ], description ];
           request.input[ "startDate", sql.Date, startDate ];
           request.input[ "startTime", sql.Time, startTime ];
           request.input[ "endDate", sql.Date, endDate ];
           request.input[ "endTime", sql.Time, endTime ];
           return request.query[ sqlQueries.addEvent ];
       };
    
       const updateEvent = async [ { id, userId, title, description, startDate, startTime, endDate, endTime } ] => {
           const pool = await getConnection[];
           const request = await pool.request[];
           request.input[ "id", sql.Int, id ];
           request.input[ "userId", sql.VarChar[ 50 ], userId ];
           request.input[ "title", sql.NVarChar[ 200 ], title ];
           request.input[ "description", sql.NVarChar[ 1000 ], description ];
           request.input[ "startDate", sql.Date, startDate ];
           request.input[ "startTime", sql.Time, startTime ];
           request.input[ "endDate", sql.Date, endDate ];
           request.input[ "endTime", sql.Time, endTime ];
           return request.query[ sqlQueries.updateEvent ];
       };
    
       const deleteEvent = async [ { id, userId } ] => {
           const pool = await getConnection[];
           const request = await pool.request[];
           request.input[ "id", sql.Int, id ];
           request.input[ "userId", sql.VarChar[ 50 ], userId ];
           return request.query[ sqlQueries.deleteEvent ];
       };
    
       return {
           addEvent,
           deleteEvent,
           getEvents,
           updateEvent
       };
    };
    
    module.exports = { register };

    Cập nhật  src/routes/api/events.js để chứa mã sau.

    "use strict";
    
    const boom = require[ "boom" ];
    
    module.exports.register = async server => {
       server.route[ {
           method: "GET",
           path: "/api/events",
           config: {
               auth: {
                   strategy: "session",
                   mode: "required"
               },
               handler: async request => {
                   try {
                       // get the sql client registered as a plugin
                       const db = request.server.plugins.sql.client;
    
                       // get the current authenticated user's id
                       const userId = request.auth.credentials.profile.id;
    
                       // execute the query
                       const res = await db.events.getEvents[ userId ];
    
                       // return the recordset object
                       return res.recordset;
                   } catch [ err ] {
                       server.log[ [ "error", "api", "events" ], err ];
                       return boom.boomify[ err ];
                   }
               }
           }
       } ];
    
       server.route[ {
           method: "POST",
           path: "/api/events",
           config: {
               auth: {
                   strategy: "session",
                   mode: "required"
               },
               handler: async request => {
                   try {
                       const db = request.server.plugins.sql.client;
                       const userId = request.auth.credentials.profile.id;
                       const { startDate, startTime, endDate, endTime, title, description } = request.payload;
                       const res = await db.events.addEvent[ { userId, startDate, startTime, endDate, endTime, title, description } ];
                       return res.recordset[ 0 ];
                   } catch [ err ] {
                       server.log[ [ "error", "api", "events" ], err ];
                       return boom.boomify[ err ];
                   }
               }
           }
       } ];
    
       server.route[ {
           method: "DELETE",
           path: "/api/events/{id}",
           config: {
               auth: {
                   strategy: "session",
                   mode: "required"
               },
               response: {
                   emptyStatusCode: 204
               },
               handler: async request => {
                   try {
                       const id = request.params.id;
                       const userId = request.auth.credentials.profile.id;
                       const db = request.server.plugins.sql.client;
                       const res = await db.events.deleteEvent[ { id, userId } ];
                       return res.rowsAffected[ 0 ] === 1 ? "" : boom.notFound[];
                   } catch [ err ] {
                       server.log[ [ "error", "api", "events" ], err ];
                       return boom.boomify[ err ];
                   }
               }
           }
       } ];
    };

    Thêm Vue.js

    Đầu tiên, hãy cài đặt các gói phụ thuộc cho Vue.js và các gói khác được sử dụng cho giao diện người dùng.

    npm install  luxon@1 materialize-css@1 moment@2 vue@2 vue-datetime@latest weekstart@1

    Tạo một thư mục mới ở thư mục gốc của dự án có tên  client. Trong thư mục này, thêm một tệp mới có tên  index.js. Thêm mã sau vào tệp này.

    import Datetime from "vue-datetime";
    import Vue from "vue";
    import "materialize-css";
    import "materialize-css/dist/css/materialize.min.css";
    import "vue-datetime/dist/vue-datetime.css";
    
    import App from "./App";
    
    Vue.use[ Datetime ];
    
    new Vue[ { // eslint-disable-line no-new
     el: "#app",
     render: h => h[ App ]
    } ];

    Thêm một tệp mới vào  client tên  App.vue. Thêm mã sau vào tệp này.

    
     

    {{ msg }}

    Event List

    Start End Title Description
    {{ event.startDate }} {{ event.startTime }} {{ event.endDate }} {{ event.endTime }} {{ event.title }} {{ event.description }} deleteDelete

    No events yet!

    Add an Event

    Start Date
    Time
    End Date
    Time
    Title
    Description
    sendSubmit

    Confirm delete

    Delete {{ selectedEvent }}?

    Ok Cancel
    import axios from "axios"; import * as M from "materialize-css"; import moment from "moment"; export default { name: "app", computed: { hasEvents[] { return this.isLoading === false && this.events.length > 0; }, noEvents[] { return this.isLoading === false && this.events.length === 0; } }, data[] { return { title: "", description: "", events: [], isLoading: true, startDate: "", startTime: "", endDate: "", endTime: "", selectedEvent: "", selectedEventId: 0 }; }, methods: { addEvent[] { const event = { startDate: this.startDate ? moment[ this.startDate ].format[ "YYYY-MM-DD" ] : null, startTime: this.startTime ? moment[ this.startTime ].format[ "YYYY-MM-DD HH:mm:00" ] : null, endDate: this.endDate ? moment[ this.endDate ].format[ "YYYY-MM-DD" ] : null, endTime: this.endTime ? moment[ this.endTime ].format[ "YYYY-MM-DD HH:mm:00" ] : null, title: this.title, description: this.description }; axios .post[ "/api/events", event ] .then[ [] => { this.startDate = ""; this.startTime = ""; this.endDate = ""; this.endTime = ""; this.title = ""; this.description = ""; this.loadEvents[]; } ] .catch[ err => { this.msg = err.message; console.log[ err ]; } ]; }, confirmDeleteEvent[ id ] { const event = this.events.find[ e => e.id === id ]; this.selectedEvent = `'${ event.title }' on ${ event.startDate }${ event.startTime ? ` at ${ event.startTime }` : "" }`; this.selectedEventId = event.id; const dc = this.$refs.deleteConfirm; const modal = M.Modal.init[ dc ]; modal.open[]; }, deleteEvent[ id ] { axios .delete[ `/api/events/${ id }` ] .then[ this.loadEvents ] .catch[ err => { this.msg = err.message; console.log[ err ]; this.loadEvents[]; } ]; }, formatDate[ d ] { return d ? moment.utc[ d ].format[ "MMM D, YYYY" ] : ""; }, formatTime[ t ] { return t ? moment[ t ].format[ "h:mm a" ] : ""; }, formatEvents[ events ] { return events.map[ event => { return { id: event.id, title: event.title, description: event.description, startDate: this.formatDate[ event.startDate ], startTime: this.formatTime[ event.startTime ], endDate: this.formatDate[ event.endDate ], endTime: this.formatTime[ event.endTime ] }; } ]; }, loadEvents[] { axios .get[ "/api/events" ] .then[ res => { this.isLoading = false; this.events = this.formatEvents[ res.data ]; } ] .catch[ err => { this.msg = err.message; console.log[ err ]; } ]; } }, mounted[] { return this.loadEvents[]; } }; #app h2 { font-size: 2rem; } .datetime-label { color: #9e9e9e; font-size: .8rem; }

    Thêm quy trình xây dựng

    Cần phải tạo một quy trình xây dựng để chuyển đổi và đóng gói giao diện người dùng của khách hàng thành các định dạng tương thích với hầu hết các trình duyệt. Đối với các ứng dụng Node.js, các bước xây dựng này thường được thêm vào  package.jsontệp bên dưới  scripts.

    Trước tiên, hãy cài đặt các gói bạn sẽ cần để xây dựng các tệp khách hàng.

    npm install --save-dev nodemon@1 npm-run-all@4 parcel-bundler@1 @vue/component-compiler-utils@2 vue-template-compiler@2

    Lưu ý: Đối  --save-dev số hướng dẫn  npm cài đặt chúng dưới dạng  phụ thuộc của nhà phát triển thay vì phụ thuộc được yêu cầu cho quá trình sản xuất trong thời gian chạy.

    Bây giờ, hãy sửa đổi  package.json và thay đổi  scripts phần để phù hợp với phần sau.

     "scripts": {
       "build": "parcel build client/index.js",
       "dev:start": "npm-run-all build start",
       "dev": "nodemon --watch client --watch src -e js,ejs,sql,vue,css --exec npm run dev:start",
       "start": "node .",
       "test": "echo \"Error: no test specified\" && exit 1"
     },

    Bạn có thể chạy bất kỳ kịch bản được xác định từ lệnh / thiết bị đầu cuối sử dụng  npm run [label] nơi  label là một trong các nhãn được xác định dưới  scripts. Ví dụ, bạn có thể chạy chỉ  build bước bằng cách sử dụng  npm run build.

    Nhân tiện,  nodemon là một tiện ích tuyệt vời theo dõi các thay đổi đối với tệp và tự động khởi động lại ứng dụng Node.js. Bây giờ bạn có thể bắt đầu quá trình xây dựng mới và khởi chạy ứng dụng web bằng một lệnh.

    npm run dev
    Bản giới thiệu lịch

    Tôi hy vọng bạn thích học cách sử dụng SQL Server với Node.js! Bạn nhận được mã nguồn cuối cùng cho dự án này trên  GitHub , mã này cũng bao gồm một số tính năng bổ sung, chẳng hạn như ví dụ về các bài kiểm tra và tác vụ để tự động khởi tạo cơ sở dữ liệu SQL.

    Tìm hiểu thêm về Node.js và SQL

    Bạn muốn tìm hiểu thêm về Node.js? Kiểm tra một số tài nguyên hữu ích này!

    Theo dõi chúng tôi để biết thêm nội dung hay và cập nhật từ nhóm của chúng tôi! Bạn có thể tìm thấy chúng tôi trên  Twitter ,  Facebook và  LinkedIn . Câu hỏi? Đánh giá chúng tôi trong các bình luận bên dưới.

    Xây dựng ứng dụng Secure Node.js với SQL Server ban đầu được xuất bản trên Blog nhà phát triển Okta vào ngày 11 tháng 3 năm 2019. 

    Chủ Đề