hapiでjs mysqlとnuxt.jsは簡単な本のプロジェクトを模倣します
前言:
プレビュー:当駅プレビュー:テンセントクラウドip、ドメイン名zhanglijian.トップ(届出中...) github:https://github.com/huoguozhang/my-blog
開始: npm i mysqlを に配置する npm run server or npm run dev
実装機能:ユーザー:登録、登録、ユーザー資料の修正、ユーザー詳細ページ、簡書に類似する、文章削除 文章:文章詳細ページ、表示、評論、点賛と踏み、文章読書回数統計 文章:すべての文章、ページ分けとキーワード、時間別検索をサポートする 文章作成:markdownと画像ドラッグアップロード をサポートトップページ:記事推薦、著者推薦、トップページ輪番、トップ検索記事とユーザー ssr効果プレビュー: よく知っている seo効果: 補足対象
1テクノロジスタック:フロントエンド:axios、element-ui、nuxt.js、 ts バックエンド:node.js、hapi.js、sequelize(orm)、hapi-auth(token)、hapi-swagger(オンラインapiドキュメント)、hapi-pagination(ページング)、joi(フロントエンド要求データ検査elementのフォーム検査に類似)、mysqlおよびその他のプラグイン 2技術詳細:
説明:本文は主にバックエンドに重点を置いて、最後の効果は我が社のバックエンドに似ています
ディレクトリ構造:
前言:なぜhapiなのか.js ?
hapi公式ドキュメントはもうたくさん話しています(expresstohapi)、ここで一番惹かれたのは、たくさんのプラグインをインストールしなくてもいいことです(expresならxx-parseプラグインがたくさんあります...)、私のニーズを満たすことができ、hapiはビジネスに応用されています.
注意点:
私のこれらのコードは、私の現在のpackageです.jsonのバージョンは正常に動作することができて、hapiのバージョンの大きいバージョンは時々互換性がなくて、異なるバージョンのhapiは異なるプラグインのバージョンに対応して、だから私のバージョンと一致する必要があって、私はnuxtに出会ったことがあります.js v2.9実行加入tsが@componentを認識しない場合、2.8をインストールする.xバージョンでは問題ありません.
2.1 Sequelizeモデリング
開発バックグラウンドで最初に思いついたのはデータモデルの構築(テーブルの構築)です.デフォルトではmysqlをインストールする前に自分でデータベースを使いますが、ormというツールがあることを知らないときは、navicatのようなグラフィックツールでテーブルを構築するか、sql文で直接テーブルを構築するかを選択します.このようにするにはいくつかの欠点があります.データベースの操作記録が明確ではありません.テーブルを新規作成したり、フィールドを新規作成したりしました.後悔しました.削除しました.ormのデータ移行はgitに似ていることを後悔しました. 新しい環境を移行し、sqlで操作するのは面倒で、ormのコマンドを直接実行して自動的にテーブルを構築します. データモデルは、以前バックグラウンドプログラムがmysqlに連絡していたとき、接続プール、データの関係、テーブル構造を構築しただけでは知られていませんでした. 追加チェックコードを実行するより簡潔で明確な その他 sequelize
sequelizeはnodeです.jsのpromise ormツールは、他のデータベースもサポートする.
使用インストールプラグイン: sequelize init
sequelize-cliによってsequelizeを初期化すると、使いやすい初期化構造が得られます.
config/config.json
デフォルトの生成ファイルはconfigである.jsonファイルは、開発、テスト、生産の3つのデフォルトのテンプレート環境を構成しており、必要に応じてより多くの環境構成を追加することができます.ここではconfigを使います.js代替config.jsonは、より柔軟に変更するconfig/configを構成する.jsは以下のようにdevelopment(開発)とproduction(生産)の2つの環境のみを残す、開発環境と生産環境の構成パラメータを分離することができる.Envと.env.prodの2つの異なるファイルの中で、環境変数パラメータprocessを通じて.env.NODE_ENVで動的に区別します.
.env.devデータベース作成 移行ファイル を作成する
migrationsのディレクトリにタイムスタンプ-userが追加されます.jsの移行ファイルは、自動的に生成されるファイルには、upとdownの2つの空の関数が含まれており、upはテーブル構造の順方向変化の詳細を定義し、downはテーブル構造のロールバックロジックを定義するために使用されます.たとえばupにcreateTableのテーブル作成動作がある場合、downには対応するdropTable削除テーブル動作がセットされています.1つの操作記録に相当する.変更後のユーザー移行ファイルは次のとおりです.移行実行
sequelize db:migrateのコマンドは、最終的にmigrationsディレクトリの下での移行動作を定義し、タイムスタンプの順序で移行記述を個別に実行し、最終的にデータベース・テーブル構造の自動化を完了するのに役立ちます.また、現在のデータベースで実行されている移行履歴バージョンを記録するために、データベースにSequelizeMetaというテーブルがデフォルトで作成されます.すでに実行されているものは再実行されず、sequelize db:migrate:undoが前の移行ファイルのdownコマンドを実行できます.シードデータ 実行
同様にseedersディレクトリの下でファイルタイムスタンプ-init-userを生成します.js修正後
塗りつぶしコマンドの実行
データベースuserテーブルを表示するとレコードが多くなります.他の操作は移行と似ています.ドキュメント7定義モデルuserテーブルmodels/userを参照してください.jsインスタンス化seqlize modes/index.js
2.2 Joi要求パラメータチェック
joiはリクエストパラメータを検証できます
次の操作を行います.取付 使用は2.3 config.validate、詳細は公式ドキュメント を参照
2.3 hapiでインタフェースを書く
post:ログインインタフェース:routes/user.js
2.4インタフェースドキュメントswaggerインストール: を使用
hapi-swagger.js
server/index.js
あなたのdev.host:dev.port/docsを開いて私のオンラインを見ることができます
2.5 token認証hapi-auth-jwt 2
cookie hapiはすでにあなたの解析を手伝って、ファイルのアップロードもインストール: npm i hapi-auth-jwt2@8 構成: ドキュメント
hapi-auth-jwt2.js登録プラグイン server/index.js 効果: デフォルトでは、すべてのインタフェースがtoken認証を必要とする、ログインインタフェースなどのインタフェースをconfigすることができる.auth=falseは上のログインインタフェースに戻らず、ユーザー名とパスワードの検証に成功してtokenを生成します.
先端にtokeを持って頭に詰めればいいts要求ヘッダ追加Joiチェック
swaggerオンラインドキュメントの中国語から変化がわかります
2.6ページングhapi-paginationの追加取付 npm i hapi-pagination@3 構成 plugins/hapi-pagination.js 登録プラグイン パラメータチェック を加えるデータベースクエリー
3最後
オンラインアドレス体験フル機能へようこそ
1踏み込みまとめ:がインタフェース500に遭遇する場合、modelの操作後にエラー、例えばmodel sをキャプチャすることができる.findAll().catch(e => console.log(e)) バージョン互換性の問題に注意し、プラグインとhapiまたはnuxtバージョンの互換性 nuxt.config.tsの構成が有効でないtsc nuxtを実行することができる.config.ts手動コンパイル asyncDataでデータを要求し、絶対アドレスを書かないで、デフォルトで80ポートの を要求します.
2開発収穫基本的なバックエンド開発プロセス を熟知している.プラグインが互換性がない場合や他のニーズがある場合は、自分で英語のドキュメントを見なければなりません.英語のドキュメントを見て を薄めることができます.バックエンドの開発に必要な仕事が多いので、インタフェースから導入まで、後で仕事の中でお互いに理解しなければなりません.
3参考
掘金小冊:葉盛飛『hapiに基づくNode.js小プログラムバックエンド開発実践ガイドライン』
ps:いらっしゃいませstar^^;github: https://github.com/huoguozhang/my-blog
プレビュー:
開始:
実装機能:
1テクノロジスタック:
説明:本文は主にバックエンドに重点を置いて、最後の効果は我が社のバックエンドに似ています
ディレクトリ構造:
├── assets // ,css,
├── client // ,axios
├── components // vue
├── config //
├── layouts // nuxt
├── middleware // nuxt
├── migrations // orm
├── models // orm
├── nuxt.config.js
├── nuxt.config.ts
├── package-lock.json
├── package.json
├── pages // nuxt
├── plugins // hapi nuxt
├── routes // hapi
├── seeders //
├── server // app.js
├── static //
├── store // nuxt
├── tsconfig.json
├── uploads //
└── utils //
前言:なぜhapiなのか.js ?
hapi公式ドキュメントはもうたくさん話しています(expresstohapi)、ここで一番惹かれたのは、たくさんのプラグインをインストールしなくてもいいことです(expresならxx-parseプラグインがたくさんあります...)、私のニーズを満たすことができ、hapiはビジネスに応用されています.
注意点:
私のこれらのコードは、私の現在のpackageです.jsonのバージョンは正常に動作することができて、hapiのバージョンの大きいバージョンは時々互換性がなくて、異なるバージョンのhapiは異なるプラグインのバージョンに対応して、だから私のバージョンと一致する必要があって、私はnuxtに出会ったことがあります.js v2.9実行加入tsが@componentを認識しない場合、2.8をインストールする.xバージョンでは問題ありません.
2.1 Sequelizeモデリング
開発バックグラウンドで最初に思いついたのはデータモデルの構築(テーブルの構築)です.デフォルトではmysqlをインストールする前に自分でデータベースを使いますが、ormというツールがあることを知らないときは、navicatのようなグラフィックツールでテーブルを構築するか、sql文で直接テーブルを構築するかを選択します.このようにするにはいくつかの欠点があります.
sequelizeはnodeです.jsのpromise ormツールは、他のデータベースもサポートする.
使用
npm i sequelize-cli -D
npm i sequelize
npm i mysql2
sequelize-cliによってsequelizeを初期化すると、使いやすい初期化構造が得られます.
// npx
node_modules/.bin/sequelize init
├── config #
| ├── config.json #
├── models # model
| ├── index.js #
├── migrations #
├── seeders #
config/config.json
デフォルトの生成ファイルはconfigである.jsonファイルは、開発、テスト、生産の3つのデフォルトのテンプレート環境を構成しており、必要に応じてより多くの環境構成を追加することができます.ここではconfigを使います.js代替config.jsonは、より柔軟に変更するconfig/configを構成する.jsは以下のようにdevelopment(開発)とproduction(生産)の2つの環境のみを残す、開発環境と生産環境の構成パラメータを分離することができる.Envと.env.prodの2つの異なるファイルの中で、環境変数パラメータprocessを通じて.env.NODE_ENVで動的に区別します.
// config.js
if (process.env.NODE_ENV === 'production') {
require('env2')('./.env.prod')
} else {
require('env2')('./.env.dev')
}
const { env } = process
module.exports = {
'development': {
'username': env.MYSQL_USERNAME,
'password': env.MYSQL_PASSWORD,
'database': env.MYSQL_DB_NAME,
'host': env.MYSQL_HOST,
'port': env.MYSQL_PORT,
dialect: 'mysql',
logging: false, // mysql
timezone: '+08:00'
// "operatorsAliases": false, // , sequelize
},
'production': {
'username': env.MYSQL_USERNAME,
'password': env.MYSQL_PASSWORD,
'database': env.MYSQL_DB_NAME,
'host': env.MYSQL_HOST,
'port': env.MYSQL_PORT,
dialect: 'mysql',
timezone: '+08:00'
// "operatorsAliases": false, // , sequelize
}
}
.env.dev
# , ,
HOST = 127.0.0.1
PORT = 80
# 80, axios url
# MySQL
MYSQL_HOST = 111.111.111.111
MYSQL_PORT = 3306
MYSQL_DB_NAME =
MYSQL_USERNAME =
MYSQL_PASSWORD =
JWT_SECRET = token
npx sequelize db:create
npx migration:create --name user
migrationsのディレクトリにタイムスタンプ-userが追加されます.jsの移行ファイルは、自動的に生成されるファイルには、upとdownの2つの空の関数が含まれており、upはテーブル構造の順方向変化の詳細を定義し、downはテーブル構造のロールバックロジックを定義するために使用されます.たとえばupにcreateTableのテーブル作成動作がある場合、downには対応するdropTable削除テーブル動作がセットされています.1つの操作記録に相当する.変更後のユーザー移行ファイルは次のとおりです.
'use strict'
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable(
'user',
{
uid: {
type: Sequelize.UUID,
primaryKey: true
},
nickname: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
avatar: Sequelize.STRING,
description: Sequelize.STRING,
username: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
password: {
type: Sequelize.STRING,
allowNull: false
},
created_time: Sequelize.DATE,
updated_time: Sequelize.DATE
},
{
charset: 'utf8'
}
),
down: queryInterface => queryInterface.dropTable('user')
}
npx sequelize db:migrate
sequelize db:migrateのコマンドは、最終的にmigrationsディレクトリの下での移行動作を定義し、タイムスタンプの順序で移行記述を個別に実行し、最終的にデータベース・テーブル構造の自動化を完了するのに役立ちます.また、現在のデータベースで実行されている移行履歴バージョンを記録するために、データベースにSequelizeMetaというテーブルがデフォルトで作成されます.すでに実行されているものは再実行されず、sequelize db:migrate:undoが前の移行ファイルのdownコマンドを実行できます.
sequelize seed:create --name init-user
同様にseedersディレクトリの下でファイルタイムスタンプ-init-userを生成します.js修正後
'use strict'
const uuid = require('uuid')
const timeStamp = {
created_time: new Date(),
updated_time: new Date()
}
const users = []
for (let i = 1; i < 5; i++) {
users.push(
{
uid: uuid(), username: 'zlj' + i, password: '123', nickname: ' ' + 1, ...timeStamp
}
)
}
module.exports = {
up: queryInterface => queryInterface.bulkInsert('user', users, { charset: 'utf-8' }),
down: (queryInterface, Sequelize) => {
const { Op } = Sequelize
return queryInterface.bulkDelete('user', { uid: { [Op.in]: users.map(v => v.uid) } }, {})
}
}
塗りつぶしコマンドの実行
sequelize db:seed:all
データベースuserテーブルを表示するとレコードが多くなります.他の操作は移行と似ています.ドキュメント7定義モデルuserテーブルmodels/userを参照してください.js
const moment = require('moment')
module.exports = (sequelize, DataTypes) => sequelize.define(
'user',
{
uid: {
type: DataTypes.UUID,
primaryKey: true
},
avatar: DataTypes.STRING,
description: DataTypes.STRING,
nickname: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
created_time: {
type: DataTypes.DATE,
get () {
return moment(this.getDataValue('created_time')).format('YYYY-MM-DD HH:mm:ss')
}
},
updated_time: {
type: DataTypes.DATE,
get () {
return moment(this.getDataValue('updated_time')).format('YYYY-MM-DD HH:mm:ss')
}
}
},
{
tableName: 'user'
}
)
'use strict'
const fs = require('fs')
const path = require('path')
const uuid = require('uuid')
const Sequelize = require('sequelize')
const basename = path.basename(__filename) // eslint-disable-line
const configs = require(path.join(__dirname, '../config/config.js'))
const db = {}
const env = process.env.NODE_ENV || 'development'
const config = {
...configs[env],
define: {
underscored: true,
timestamps: true,
updatedAt: 'updated_time',
createdAt: 'created_time',
hooks: {
beforeCreate (model) {
model.uid = uuid()
}
}
}
}
let sequelize
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config)
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config)
}
fs
.readdirSync(__dirname)
.filter((file) => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js')
})
.forEach((file) => {
const model = sequelize.import(path.join(__dirname, file))
db[model.name] = model
})
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db)
}
})
db.sequelize = sequelize
db.Sequelize = Sequelize
//
//
db.user.hasMany(db.article, { foreignKey: 'uid' })
db.article.belongsTo(db.user, { foreignKey: 'author' })
db.user.hasMany(db.comment, { foreignKey: 'uid' })
db.comment.belongsTo(db.user, { foreignKey: 'author' })
db.user.hasMany(db.article_like, { foreignKey: 'uid' })
db.article_like.belongsTo(db.user, { foreignKey: 'author' })
db.article.hasMany(db.comment)
db.comment.belongsTo(db.article)
db.article.hasMany(db.article_like)
db.article_like.belongsTo(db.article)
module.exports = db
2.2 Joi要求パラメータチェック
joiはリクエストパラメータを検証できます
次の操作を行います.
# hapi v16 joi
npm i joi@14
2.3 hapiでインタフェースを書く
post:ログインインタフェース:routes/user.js
const models = require('../models')
const Joi = require('@hapi/joi')
{
method: 'POST',
path: '/api/user/login',
handler: async (request, h) => {
const res = await models.user.findAll({
attributes: {
exclude: ['password', 'created_time', 'updated_time']
},
where: {
username: request.payload.username,
// ,md5
password: request.payload.password
}
})
const data = res[0]
if (res.length > 0) {
return h.response({
code: 0,
message: ' !',
data: {
// token
token: generateJWT(data.uid),
...data.dataValues
}
})
} else {
return h.response({
code: 10,
message: ' '
})
}
},
config: {
auth: false,
tags: ['api', 'user'],
description: ' ',
validate: {
payload: {
username: Joi.string().required(),
password: Joi.string().required()
}
}
}
},
2.4インタフェースドキュメントswagger
npm i hapi-swagger@10
npm i inert@5
npm i vision@5
npm i package@1
├── plugins # hapi
| ├── hapi-swagger.js
hapi-swagger.js
// plugins/hapi-swagger.js
const inert = require('@hapi/inert')
const vision = require('@hapi/vision')
const package = require('package')
const hapiSwagger = require('hapi-swagger')
module.exports = [
inert,
vision,
{
plugin: hapiSwagger,
options: {
documentationPath: '/docs',
info: {
title: 'my-blog ',
version: package.version
},
// tags
grouping: 'tags',
tags: [
{ name: 'user', description: ' ' },
{ name: 'article', description: ' ' }
]
}
}
]
server/index.js
const pluginHapiSwagger = require('../plugins/hapi-swagger')
//
...
await server.register([
// hapi-swagger
...pluginHapiSwagger
]
...
あなたのdev.host:dev.port/docsを開いて私のオンラインを見ることができます
2.5 token認証hapi-auth-jwt 2
cookie hapiはすでにあなたの解析を手伝って、ファイルのアップロードも
├── plugins # hapi
│ ├── hapi-auth-jwt2.js # jwt
hapi-auth-jwt2.js
const validate = (decoded) => {
// eslint disable
// decoded JWT payload
const { exp } = decoded
if (new Date(exp * 1000) < new Date()) {
const response = {
code: 4,
message: ' ',
data: ' '
}
return { isValid: true, response }
}
return { isValid: true }
}
module.exports = (server) => {
server.auth.strategy('jwt', 'jwt', {
// config/index.js jwtSecret , process.env.JWT_SECRET .git 。
key: process.env.JWT_SECRET,
validate,
verifyOptions: {
ignoreExpiration: true
}
})
server.auth.default('jwt')
}
const hapiAuthJWT2 = require('hapi-auth-jwt2')
...
await server.register(hapiAuthJWT2)
...
const generateJWT = (uid) => {
const payload = {
userId: uid,
exp: Math.floor(new Date().getTime() / 1000) + 24 * 60 * 60
}
return JWT.sign(payload, process.env.JWT_SECRET)
}
handler () {
const res = await models.user.findAll({
attributes: {
exclude: ['password', 'created_time', 'updated_time']
},
where: {
username: request.payload.username,
password: request.payload.password
}
})
const data = res[0]
if (res.length > 0) {
return h.response({
code: 0,
message: ' !',
data: {
token: generateJWT(data.uid),
...data.dataValues
}
})
} else {
return h.response({
code: 10,
message: ' '
})
}
}
先端にtokeを持って頭に詰めればいいts
request.interceptors.request.use((config: AxiosRequestConfig): AxiosRequestConfig => {
const token = getToken()
if (token) { config.headers.authorization = token }
return config
})
const jwtHeaderDefine = {
headers: Joi.object({
authorization: Joi.string().required()
}).unknown()
}
//
...
validate: {
...jwtHeaderDefine,
params: {
uid: Joi.string().required()
}
}
...
swaggerオンラインドキュメントの中国語から変化がわかります
2.6ページングhapi-paginationの追加
const hapiPagination = require('hapi-pagination')
const options = {
query: {
page: {
name: 'the_page' // The page parameter will now be called the_page
},
limit: {
name: 'per_page', // The limit will now be called per_page
default: 10 // The default value will be 10
}
},
meta: {
location: 'body', // The metadata will be put in the response body
name: 'metadata', // The meta object will be called metadata
count: {
active: true,
name: 'count'
},
pageCount: {
name: 'totalPages'
},
self: {
active: false // Will not generate the self link
},
first: {
active: false // Will not generate the first link
},
last: {
active: false // Will not generate the last link
}
},
routes: {
include: ['/article'] //
}
}
module.exports = {
plugin: hapiPagination,
options
}
const pluginHapiPagination = require('./plugins/hapi-pagination');
await server.register([
pluginHapiPagination,
])
const paginationDefine = {
limit: Joi.number().integer().min(1).default(10)
.description(' '),
page: Joi.number().integer().min(1).default(1)
.description(' '),
pagination: Joi.boolean().description(' , true')
}
//
// joi
...
validate: {
query: {
...paginationDefine
}
}
...
const { rows: results, count: totalCount } = await models.xxxx.findAndCountAll({
limit: request.query.limit,
offset: (request.query.page - 1) * request.query.limit,
});
3最後
オンラインアドレス体験フル機能へようこそ
1踏み込みまとめ:
2開発収穫
3参考
掘金小冊:葉盛飛『hapiに基づくNode.js小プログラムバックエンド開発実践ガイドライン』
ps:いらっしゃいませstar^^;github: https://github.com/huoguozhang/my-blog