Food TruckIs based onJavascript(Typescript)The development of the food truck ordering system, the system includesSMS registration.order.Place an order to payThe development process is divided into 7 steps, each step inGithubHave branches in the code repository (fromphase/1phase/7) for easy reference.

Food Truck is an online food ordering app based on Javascript(Typescript), this system covers the flow from registering, menu navigating and placing orders. I split this flow into 7 steps, each step corresponds to a branch of this repository.



  • Day 1 Building scaffolding for the back-end and front-end projects

  • Day 2 refactoring the back-end project, introducing state management in the front end, and introducing state management into the front-end project

  • Day 3 UI design and developing UI at the front end

  • Day 4 SMS registration and login Signin/Signup with OTP

  • Day 5 Database support

  • Day 6 Making a Payment with Stripe

  • Day 7 Continuous Integration and Continuous Deployment (CI/CD)

  • Side Others

While it would be an exaggeration to say that a full-stack application can be developed from start to finish in 7 days, this article shows how to efficiently build an application using mature cloud native technologies. This is for those who have some development experienceprimaryThis tutorial does not cover every technical point in depth, but covers all aspects of developing a full-stack application, including continuous integration (CI).CI/CD).

Although it is an exaggeration to develop a full-stack application in 7 days, but this article shows how to use mature cloud technology to build an application possibility. This is a primary tutorial for new bees with a little development experience, so it will not deep dive every technical point, but it will cover all aspects of developing a full-stack application, including continuous integration (CI/ CD).



The server uses itNode.jsKoa2Framework. User authentication is based onAWSCognitoTo register the background involved in loginLambda FunctionIt’s in the front-end engineering, byAWS AmplifyProvide support. Payment adoptedStripeServices provided.

The server uses the Node.js and Koa2 frameworks. User authentication is based on Cognito of AWS, the backend Lambda Functioninvolved in registration and login is in the front-end project and is supported by AWS Amplify. Payment uses the service provided byStripe.



Front end USESVue3, considering the compatibility of third-party libraries, not adoptedViteAs a development and packaging tool.

The front-end uses Vue3



As an introduction, this article provides a starting point for everyone to learn and communicate. If you are interested, please add wechat and communicate with me.

As an introduction, this article provides a starting point for everyone to learn and communicate. It is welcome to add WeChat to communicate with the author.The related code repository is as follows:

The relevant code repositories are as follows:

The backend repository is back-end

Front-end repository front-end

Shared library Shared library

Code warehouse is not open for the time being, students who need to exchange learning can add contact information.

Day 1 Set up the scaffolding for the front and back end projects

“To do a good job, you must sharpen your tools.”

If a worker wants to do his job well, he must first sharpen his tools

  • Efficient development environment and smooth continuous integration process determine the efficiency of development. Installation is recommended on MacOSiTerm2.Oh-My-ZshAnd configurePowerlevel10KThe installation of these tools will not be described in detail here, but those who are interested can search on the Internet. This is my development environment.

An efficient development environment and a smooth continuous integration process guarantees the efficiency of development. Under MacOS, it is recommended to install iTerm2.Oh-My-Zsh and then configure the theme of Powerlevel10K. The installation of these tools will not be discussed here, if you are interested in these tools, you can google it. This is my development environment.

  • If a team is involved in the development of a project, it is also important to agree on code specifications during development, which will be highlighted in this sectionESLintPrettierTo standardize the team’s code.

It is very important to unify the code style during development if a team is involved in a project. This section will focus on the configuration of ESLint and Prettier to standardize the team’s code style.

Create a backend project from scratch from zero to one

  1. In the firstGithubCreate a repository onft-node

create ft-node repository on Github first

  • Initializing the localgitWarehouse and push toGithubIn the warehouse

initialize the local repository, and push it onto the remote repository under Github

mkdir backend && cd backend
echo "# ft-node" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin [email protected]:quboqin/ft-node.git
git push -u origin main
Copy the code
  • add.gitignore

add .gitignore file

# .gitignore
# Dependency directory
node_modules

# Ignore built ts files
dist
Copy the code
  1. Initialize thenpmTypescriptproject

Intialize the node.js project with npm, and configure Typescript

#Create code directory
mkdir src
#Initialize the NPM project
npm init -y
#Install Typescript locally before initializing the Typescript project
npm install typescript --save-dev
#Initialize the typescript project
npx tsc --init --rootDir src --outDir dist \
--esModuleInterop --target esnext --lib esnext \
--module commonjs --allowJs true --noImplicitAny true \
--resolveJsonModule true --experimentalDecorators true --emitDecoratorMetadata true \
--sourceMap true --allowSyntheticDefaultImports true
Copy the code
  1. The installationKoa.Koa Router@Koa/cors(cross-domain support), and corresponding type dependencies
npm i koa koa-router @koa/cors
npm i @types/koa @types/koa-router types/koa__cors --save-dev
Copy the code
  1. Create a simpleKoaservice
  • Create the server.ts file in the SRC directory
// server.ts
import Koa from 'koa'
import Router from 'koa-router'
import cors from '@koa/cors'

const app = new Koa()
const router = new Router()

app.use(cors())

router.get('/checkHealth'.async (ctx) => {
  ctx.body = {
    code: 0.data: 'Hello World! ',
  }
})

app.use(router.routes())

app.listen(3000)

console.log('Server running on port 3000')
Copy the code
  • test
npx tsc
node dist/server.js
Server running on port 3000
Copy the code
  1. The installationts-nodenodemonRun the server with Nodemon and TS-node, and modify the valuepackage.jsonThe script in the
npm i ts-node nodemon --save-dev
#If typescript is installed in a global environment
# npm link typescript
Copy the code

Modify the package. The json

  "scripts": {
    "dev": "nodemon --watch 'src/**/*' -e ts,tsx --exec ts-node ./src/server.ts"."build": "rm -rf dist && tsc"."start": "node dist/server.js"
  },
Copy the code
  1. configurationESLintPrettier

Lint

  • The installationeslintAnd related dependencies
npm i eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
Copy the code
  • eslint: The core ESLint linting library
  • @typescript-eslint/parser: The parser that will allow ESLint to lint TypeScript code
  • @typescript-eslint/eslint-plugin: A plugin that contains a bunch of ESLint rules that are TypeScript specific
  • Add the.eslintrc.js configuration file

It can be created interactively

npx eslint --init
Copy the code

However, it is recommended to manually create this file in the root directory of your project

// .eslintrc.js
module.exports = {
  parser: '@typescript-eslint/parser'.// Specifies the ESLint parser
  parserOptions: {
    ecmaVersion: 2020.// Allows for the parsing of modern ECMAScript features
    sourceType: 'module'.// Allows for the use of imports
  },
  extends: [
    'plugin:@typescript-eslint/recommended'.// Uses the recommended rules from the @typescript-eslint/eslint-plugin].rules: {
    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
    // e.g. "@typescript-eslint/explicit-function-return-type": "off",}},Copy the code
  • addPrettier
npm i prettier eslint-config-prettier eslint-plugin-prettier --save-dev
Copy the code
  • prettier: The core prettier library
  • eslint-config-prettier: Disables ESLint rules that might conflict with prettier
  • eslint-plugin-prettier: Runs prettier as an ESLint rule

In order to configure prettier, a .prettierrc.js file is required at the root project directory. Here is a sample .prettierrc.js file:

// .prettierrc.js
module.exports = {
  tabWidth: 2.semi: false.singleQuote: true.trailingComma: 'all',}Copy the code

Next, the .eslintrc.js file needs to be updated:

// eslintrc.js
extends: [
  "plugin:@typescript-eslint/recommended".// Uses the recommended rules from the @typescript-eslint/eslint-plugin
  "plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.].Copy the code
  • Automatically Fix Code in VS Code
    • The installationeslintExtension, no installation required hereprettierextension

And click in the lower right corner to activate the ESLint extension

  • Create the Workspace configuration in VSCode
{
  "editor.formatOnSave": false."editor.codeActionsOnSave": {
    "source.fixAll.eslint": true}},Copy the code
  • Run ESLint with the CLI
    • A useful command to add to the package.json scripts is a lint command that will run ESLint.
{
  "scripts": {
    "lint": "eslint '*/**/*.{js,ts,tsx}' --quiet --fix"}}Copy the code
  • add.eslintignore
# don't ever lint node_modules
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
dist
Copy the code
  • Preventing ESLint and formatting errors from being committed
npm install husky lint-staged --save-dev
Copy the code

To ensure all files committed to git don’t have any linting or formatting errors, there is a tool called lint-staged that can be used. lint-staged allows to run linting commands on files that are staged to be committed. When lint-staged is used in combination with husky, the linting commands specified with lint-staged can be executed to staged files on pre-commit (if unfamiliar with git hooks, read about them here). To configure lint-staged and husky, add the following configuration to the package.json file:

{
  "husky": {
      "hooks": {
          "pre-commit": "lint-staged"
      }
  },
  "lint-staged": {
      "*.{js,ts,tsx}": [
          "eslint --fix"
      ]
  }
}
Copy the code

Initialize theVue3The front end

  1. Create front-end projects using the Vue Cli
#See the Vue CLI version
vue --version
#Update the latest version
npm update -g @vue/cli
#Create a project
vue create frontend
Copy the code

  1. withVue CliMost of the scaffolding created has been configuredESLintPrettierI just need to unify the front and back endsPrettierSet up the
  • Add the.prettierrc.js file
// .prettierrc.js
module.exports = {
  tabWidth: 2.semi: false.singleQuote: true.trailingComma: 'all',}Copy the code
  • Modify the workspace configuration as well
{
  "editor.formatOnSave": false."editor.codeActionsOnSave": {
    "source.fixAll.eslint": true}}Copy the code
  1. Run NPM run Lint

Axois is introduced in the front end, accessing the health Interface

  1. Install the AXIOS module
npm i axios --save
Copy the code
  1. Encapsulate the AXIos module and set the default URL
// src/utils/axios.ts
axios.defaults.baseURL = 'http://localhost:3000'
Copy the code
  1. Encapsulate the SRC /apis/health.ts file
// src/apis/health.ts
import { result } from '@/utils/axios'

export function checkHealth<U> () :Promise<U | void> {
  return result('get'.'/checkHealth')}Copy the code
  1. Call the checkHealth interface in app.vue
<script lang="ts">
import { defineComponent, onMounted } from 'vue'

import { checkHealth } from '@/apis/health'

export default defineComponent({
  name: 'App',
  setup() {
    const init = async () => {
      checkHealth()
    }
    onMounted(init)
  },
})
</script>
Copy the code

At this stage, I completed the construction of the front and back end of the simplest APP. Here I have standardized the front and back end code, students can add different rules according to their own team code specification.

Javascript & Typescript Essential

Day 2 Refactoring back-end projects to introduce state management at the front end

Reconstructing the Server

Importing the server log libraryloggerwinston

#Logger acts as the middleware for logging requests
npm install koa-logger --save
npm install @types/koa-logger --save-dev
#Winston for logging elsewhere in the back end
npm install winston --save
Copy the code

Results the following

The introduction ofdotenvRead configurations from environment variables and environment variable configuration files

  1. Node’s back-end service dynamically loads these environment variables at runtime. I used the Dotenv module to load environment variables in the code
npm install dotenv --save
Copy the code
  1. Reading environment variables
// src/config/index.ts
import { config as dotenv } from 'dotenv'

const env = dotenv({ path: `.env.${process.env.NODE_ENV}` })
if (env.error) throw env.error
Copy the code
  1. ** Two points **
  • ifNODE_ENVSet toproduction.npm ciDon’t installdevDependenciesDependency packages, if runningEC2Compile package, compile will report an error. So the package compilation I put inGithub ActionsSo this problem is avoided

We have installed all our packages using the –save-dev flag. In production, if we use npm install –production these packages will be skipped.

  • Env files are overwritten by environment variables set in the operating system. Sensitive information, such as passwords and keys, is generally defined in the system environment variables, rather than in the system environment variables.envThis avoids the security implications of submitting code to a public repository.
  1. The loading logic of the configuration file is placed inconfigdirectory

The Server disconnects the App and Server

  1. index.tsOnly the boot is preservedhttpServices and other services that connect to the database
// src/index.ts
import * as http from 'http'
import app from './app'

import wsLogger from './utils/ws-logger'

async function initializeDB() :Promise<void> {
  wsLogger.info(`Connect database... `)

  return
}

async function bootstrap(port: number) {
  const HOST = '0.0.0.0'

  return http.createServer(app.callback()).listen(port, HOST)
}

try {
  ;(async function () :Promise<void> {
    await initializeDB()

    const HTTP_PORT = 3000
    await bootstrap(HTTP_PORT)

    wsLogger.info('🚀 Server listening on port${HTTP_PORT}! `()})})catch (error) {
  setImmediate(() = > {
    wsLogger.error(
      `Unable to run the server because of the following error: ${error}`,
    )
    process.exit()
  })
}
Copy the code
  1. app.tsResponsible for various middleware loading and installationbodyParser
npm install koa-bodyparser --save
npm install @types/koa-bodyparser --save-dev
Copy the code
// src/app.ts
import Koa from 'koa'
import cors from '@koa/cors'
import logger from 'koa-logger'
import bodyParser from 'koa-bodyparser'

import wrapperRouter from './apis'

const app = new Koa()

app.use(cors())
app.use(logger())
app.use(bodyParser())

const wpRouter = wrapperRouter()
app.use(wpRouter.routes()).use(wpRouter.allowedMethods())

export default app
Copy the code

Set upRCSModel,server.tsSplit intohttpService,Koa2Middleware andkoa-routerThree modules, in line with the design principle of a single responsibility

the logic should be divided into these directories and files.

  • Models – The schema definition of the Model
  • Routes – The API routes maps to the Controllers
  • Controllers – The controllers handles all the logic behind validating request parameters, query, Sending Responses with correct codes.
  • Services – The services contains the database queries and returning objects or throwing errors
  1. routerApart toapisIn the directory, different interfaces are provided by modules and loaded dynamically
// src/apis/index.ts
import * as fs from 'fs'
import * as path from 'path'
import Router from 'koa-router'

import { serverConfig } from '.. /config/server'
const { apiVersion } = { apiVersion: serverConfig.apiVersion }

export default function wrapperRouter() :Router {
  const router = new Router({
    prefix: `/api/${apiVersion}`,})const baseName = path.basename(__filename)

  fs.readdirSync(__dirname)
    // Filter out the index.js and index.js.map files in the root directory of apis
    .filter(
      (file) = >
        file.indexOf('. ')! = =0&& file ! == baseName && ! file.endsWith('.map'),
    )
    .forEach((file) = > {
      import(path.join(__dirname, file)).then((module) = > {
        router.use(module.router().routes())
      })
    })

  return router
}
Copy the code
  1. The breakdown of each sub-module under apis is shown below

Construct front-end routes and introduce state management Provide and Inject of Vue 3.0

Use Provide/Inject in vue.js 3 with the Composition API to replace Vuex with Provide and Inject

Building and publishing an NPM typescript package

See Step by Step: Building and publishing an NPM Typescript package.

  1. Create a directory
mkdir lib && cd lib
Copy the code
  1. Initialize thegitwarehouse
git init
git checkout -b main
echo "# Building and publishing an npm typescript package" >> README.md
git add . && git commit -m "Initial commit"
Copy the code
  1. Initialize thenpmproject
npm init -y
echo "node_modules" >> .gitignore
npm install --save-dev typescript
Copy the code
  1. configurationtsconfig.json
{
  "compilerOptions": {
    "target": "es2017"."module": "commonjs"."outDir": "./lib"."sourceMap": true."strict": true."noImplicitAny": true."declaration": true."strictPropertyInitialization": false."experimentalDecorators": true /* Enables experimental support for ES7 decorators. */."emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */
  },
  "include": ["src"]."exclude": ["node_modules"."**/__tests__/*"]}Copy the code
  1. ignore /lib
echo "/lib" >> .gitignore
Copy the code
  1. configurationeslint
  • Install ESLint and typescript dependencies
npm i eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
Copy the code
  • create.eslintrc.js
// .eslintrc.js
module.exports = {
  parser: '@typescript-eslint/parser'.extends: ['plugin:@typescript-eslint/recommended'.'prettier/@typescript-eslint'.'plugin:prettier/recommended'].parserOptions: {
    ecmaVersion: 2018.sourceType: 'module',},rules: {},}Copy the code
  • The installationprettier
npm i prettier eslint-config-prettier eslint-plugin-prettier -D
Copy the code
  • create.prettierrc.js
// .prettierrc.js
module.exports = {
  semi: false.trailingComma: 'all'.singleQuote: true.printWidth: 120.tabWidth: 2,}Copy the code
  • Configuration vscode workspace
{
  "editor.formatOnSave": false."editor.codeActionsOnSave": {
    "source.fixAll.eslint": true}},Copy the code
  • Add the eslintignore
node_modules
/lib
Copy the code
  1. Configure the output file of the NPM library

However, blacklisting files is not a good practice. Every new file/folder added to the root, needs to be added to the .npmignore file as well! Instead, you should whitelist the files /folders you want to publish. This can be done by adding the files property in package.json:

  "files": [
    "lib/**/*"].Copy the code
  1. Setup Testing with Jest
  • The installationJest
npm install --save-dev jest ts-jest @types/jest
Copy the code
  • Create a new file in the root and name it jestconfig.json:
{
  "transform": {
    "^.+\\.(t|j)sx? $": "ts-jest"
  },
  "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx? |tsx?) $"."moduleFileExtensions": ["ts"."tsx"."js"."jsx"."json"."node"]}Copy the code
  • Remove the old test script in package.json and change it to:
"test": "jest --config jestconfig.json".Copy the code
  • Prepare a function to testindex.ts
// index.ts
export const Greeter = (name: string) :string= > `Hello ${name}`
Copy the code
  • Write a basic test

In the src folder, add a new folder called tests and inside, add a new file with a name you like, but it has to end with test.ts, for example greeter.test.ts

// greeter.test.ts
import { Greeter } from '.. /index';
test('My Greeter'.() = > {
  expect(Greeter('Carl')).toBe('Hello Carl');
});
Copy the code

then try to run

npm test
Copy the code
  1. Use the magic scripts in NPM
  • Modify the scriptpackage.json
    "prepare": "npm run build"."prepublishOnly": "npm test && npm run lint"."preversion": "npm run lint"."version": "git add -A src"."postversion": "git push && git push --tags"."patch": "npm version patch && npm publish"
Copy the code
  • Updating your published package version number

To change the version number in package.json, on the command line, in the package root directory, run the following command, replacing <update_type> with one of the semantic versioning release types (patch, major, or minor):

npm version <update_type>
Copy the code
  • Run npm publish.
  1. Finishing up package.json
  "description": "share library between backend and frontend"."main": "lib/index.js"."types": "lib/index.d.ts".Copy the code
  1. Publish you package to NPM!
  • run npm login
╰ ─ NPM login ─ ╯ NPM notice the Log in on https://registry.npmjs.org/ Username: quboqin Password: Email: (this IS public) [email protected] Logged in as quboqin on https://registry.npmjs.org/.Copy the code
  1. install quboqin-lib on both sides(frontend and backend), and add test code
npm i quboqin-lib
Copy the code

Day 3 front-end interface design and development

Create data model, updatequboqin-lib

The front end introduces Tailwind CSS UI library

  1. withFigmaDesign a PROTOTYPE UI

2. InstallTailwindThis is a based onutility-firstThought UI library, on its basis can be easy to build UI interface, basically do not have to write a line in the codecssIt is also convenient for later maintenance

Pay attention to the compatibility of PostCSS 7

  • The installation
npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
Copy the code
  • Add Tailwind as a PostCSS plugin

Add tailwindcss and autoprefixer to your PostCSS configuration.

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},}}Copy the code
  • Customize your Tailwind installation
npx tailwindcss init
Copy the code
  • Include Tailwind in your CSS
/* index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Copy the code

then import this file in main.ts

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

import './index.css'

createApp(App).use(router).mount('#app')
Copy the code
  • When building for production, be sure to configure the purge option to remove any unused classes for the smallest file size:
// tailwind.config.js
  module.exports = {
   purge: [
     './src/**/*.html'.'./src/**/*.js',].darkMode: false.// or 'media' or 'class'
    theme: {
      extend: {},},variants: {},
    plugins: [],}Copy the code
  1. Other Component libraries

Tailwind can be used to develop UI components. For rapid development, Vant was introduced in this project, which is an early UI library that supports Vue 3

# Install Vant 3 for Vue 3 project
npm i vant@next -S
Copy the code
  1. With the help ofmaterialdesigniconsIn thehtmladd
<link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
Copy the code

encapsulationAxiosAnd interfaces are providedMockdata

// src/apis/goods.ts
import goods from '@/mock/goods.json'

export function getAllGoods() :Promise<Good[] | void> {
  return result('get'.'/goods'.null, goods as Good[])
}
Copy the code

Local HTTPS support [Server]

  1. In the projectrootDirectory creationlocal-sslSubdirectory, inlocal-sslCreate a configuration file in the subdirectoryreq.cnf
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = US
ST = California
L = Folsom
O = MyCompany
OU = Dev Department
CN = www.localhost.com
[v3_req]
keyUsage = critical, digitalSignature, keyAgreement
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = www.localhost.com
DNS.2 = localhost.com
DNS.3 = localhost
Copy the code
  1. inlocal-sslCreate a local certificate and private key in the directory
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout cert.key -out cert.pem -config req.cnf -sha256
Copy the code
  1. Modify theserverSide code support localhttps
// src/index.ts
  interface CERT_FILE {
    key: string
    cert: stringca? :string
  }

  const certPaths: Record<string, CERT_FILE> = {}

  certPaths[`${SERVER.LOCALHOST}`] = {
    key: './local-ssl/cert.key'.cert: './local-ssl/cert.pem',}const httpsOptions = {
    key: fs.readFileSync(certPaths[config.serverConfig.server].key),
    cert: fs.readFileSync(certPaths[config.serverConfig.server].cert),
  }

  return https.createServer(httpsOptions, app.callback()).listen(port, HOST)
Copy the code
  1. Start thehttpsAn error is reported after service
curl https://localhost:3000/api/v1/users
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above
Copy the code

If you connect to the HTTPS service in the browser, an error is reported. Download the certificate, double-click the import key manager, and manually trust the certificate

  1. The front-end interface is also changedhttpsaccess
// src/utils/axios.ts
axios.defaults.baseURL = 'https://localhost:3000/api/v1'
Copy the code

Day 4 Mobile login and registration

AWS uses services such as Cognito and Amplify for user authentication and mobile logins

Initialize in front end engineeringAmplifyCognito

  1. Initialize theAmplify
amplify init
Copy the code

It’s created by defaultdevThe back-end environment

  1. addAuthservice
amplify add auth
Copy the code

** must chooseManual Configuration**, the specific parameters are as followsOtherwise, the Cognito background configuration uses the Username authentication mode by default and cannot be changed

  1. addPreSignupFunction of [selectManual ConfigurationCan be added from the command line, skip this step]
amplify fucntion add
Copy the code

  1. Update the logic of the four Lambda Functions

  2. Push dev environment

amplify push
Copy the code
  1. Enter theAWS ConsoleCognito, manual bindingPreSignupFunction [selectManual ConfigurationAlready bound on the command line, this step can be skipped.]

  1. toCreateAuthChallengeExample Add the permission to send SMS messages

Enter theLambdaThe background page, find the corresponding environmentdevfoodtruck4d6aa6e5CreateAuthChallengeThe function finds the corresponding Role and adds the SNS permission to the background of the Role

  1. Configuration of SNS

  1. The front-end addAmplifyThe module
  • Installing dependency packages
npm i aws-amplify aws-amplify-vue --save
Copy the code
  • Initialize theAmplifyconfiguration
// main.ts
import Amplify from 'aws-amplify'
import awsconfig from './aws-exports'
Amplify.configure(awsconfig)
Copy the code

** added in tsconfig.json

"allowJs": true.Copy the code

Add the aws – exports. Which s

// eslint-disable-next-line @typescript-eslint/ban-types
declare const awsmobile: {}
export default awsmobile
Copy the code

Both steps are required, otherwise packaging will report the following error

TS7016: Could not find a declaration file for module './aws-exports'. '/Users/qinqubo/magic/projects/full-stack/food-truck/frontend/src/aws-exports.js' implicitly has an 'any' type.
    4 |
    5 | import Amplify from 'aws-amplify'
  > 6 | import awsconfig from './aws-exports'
      |                       ^^^^^^^^^^^^^^^
    7 | Amplify.configure(awsconfig)
    8 |
    9 | import './index.css'
Copy the code
  1. The rest of the team is developing in a new environment

Clone the repository in the project root directory

amplify init
Copy the code

Do you want to use an existing environmentYes

Back-end project addedJWTThe module

  1. addJWTMiddleware dependencies of
npm i jsonwebtoken jwk-to-pem --save
npm i @types/jsonwebtoken @types/jwk-to-pem --save-dev
Copy the code
  1. addJWTMiddleware, checkcognitoThe effectiveness of the
// src/jwt/cognito.ts
Copy the code
  1. Modify the app. Ts
// src/app.ts
const unprotectedRouter = wrapperRouter(false)
const protectedRouter = wrapperRouter(true)

app.use(unprotectedRouter.routes()).use(unprotectedRouter.allowedMethods())
app.use(jwtCognito(config.awsCognitoConfig))
app.use(protectedRouter.routes()).use(protectedRouter.allowedMethods())

export default app
Copy the code
  1. Apis directory divided into protected and unprotected directories, and modify the route loading logic
// src/apis/index.ts
export default function wrapperRouter(isProtected: boolean) :Router {
  const router = new Router({
    prefix: `/api/${apiVersion}`,})const subFolder = path.resolve(
    __dirname,
    isProtected ? './protected' : './unprotected'.)// Require all the folders and create a sub-router for each feature api
  fs.readdirSync(subFolder).forEach((file) = > {
    import(path.join(subFolder, file)).then((module) = > {
      router.use(module.router().routes())
    })
  })

  return router
}
Copy the code

With the Postman test

  1. Set content-type to Application /json

  1. Set the Token

  1. Query parameters
https://localhost:3000/api/v1/goods
https://localhost:3000/api/v1/users
Copy the code

Day 5 database support

PostgresAnd data model

  1. Back-end project installationpostgresrelatedpackage
npm i typeorm reflect-metadata pg --save
Copy the code
  1. Modify shared libraries
  • Install in a shared librarytypeorm
npm i typeorm --save
Copy the code

Then update the dependencies on the front and back ends

  1. Install in the back-end projectuuid
npm i uuid --save
npm i @types/uuid --save-dev
Copy the code
  1. Connect to the database through Docker
  • Add the docker – compose. Yml
version: '3.1'
services:
  db:
    image: postgres:10-alpine
    ports:
      - '5432:5432'
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: apidb
  admin:
    image: adminer
    restart: always
    depends_on:
      - db
    ports:
      - 8081: 8080
Copy the code
  • Add the Postgres configuration and connection code
// src/config/index.ts
export async function initializePostgres() :Promise<Connection | void> {
  if (postgresConfig) {
    try {
      return createConnection({
        type: 'postgres'.url: postgresConfig.databaseUrl,
        ssl: postgresConfig.dbsslconn
          ? { rejectUnauthorized: false }
          : postgresConfig.dbsslconn,
        synchronize: true.logging: false.entities: postgresConfig.dbEntitiesPath,
      })
    } catch (error) {
      logger.error(error)
    }
  }
}
Copy the code
  • The importgoodsdata
npm run import-goods
Copy the code

JestTest the service layer and database

  1. The installationJest
npm i jest @types/jest ts-jest --save-dev
Copy the code
  1. Configuration Jest
// jest.config.js
/* eslint-disable max-len */
module.exports = {
  clearMocks: true.maxWorkers: 1.preset: 'ts-jest'.testEnvironment: 'node'.testMatch: [
    '**/__tests__/**/*.[jt]s? (x)'.'! **/__tests__/coverage/**'.'! **/__tests__/utils/**'.'! **/__tests__/images/**'.* * / / / "? (*) +(spec|test).[tj]s? (x)"].globalSetup: './src/utils/jest.setup.ts',}Copy the code
// ormconfig.js
module.exports = {
  name: 'default'.type: 'postgres'.host: 'localhost'.port: 5432.username: 'user'.password: 'pass'.database: 'test'.logging: false.synchronize: true.entities: ['node_modules/quboqin-lib/lib/**/*.js'],}Copy the code
  1. Add two test cases under SRC /tests

Axois does not support URL Path Variable,.get(‘/:phone’, controller.getUser) routes cannot be accessed directly through axois. Query Parameter is used to extract phone

  1. Path parameters

** Requests from Axois do not support this approach ** You can test such interfaces in browsers and Postman

https://localhost:3000/api/v1/addresses/24e583d5-c0ff-4170-8131-4c40c8b1e474
Copy the code

The corresponding route is

  router
    .get('/:id', controller.getAddress)
Copy the code

The controller below shows how to get parameters. Okay

  public static async getAddress(ctx: Context): Promise<void> {
    const { id } = ctx.params
    const address: Address = await postgre.getAddressById(id)

    const body = new Body()
    body.code = ERROR_CODE.NO_ERROR

    if (address) {
      body.data = address
    } else {
      body.code = ERROR_CODE.UNKNOW_ERROR
      body.message = `the card you are trying to retrieve doesn't exist in the db`
    }

    ctx.status = 200
    ctx.body = body
  }
Copy the code
  • How to test the interface in Postman now

Set content-type to Application /json

Set the Token

  1. Query parameters
https://localhost:3000/api/v1/addresses?phone=%2B13233013227&id=24e583d5-c0ff-4170-8131-4c40c8b1e474
Copy the code
  1. Koa2 corresponds to the value returned by Axios. Here is the pseudocode
// Koa ctx: Context
interface ctx {
  status,
  body: {
    code,
    data,
    message
  }
}

// Axois response
interface response {
  status,
  data
}
Copy the code

Response.data corresponds to CTx. body, so after Axios gets response, it gets the final result from Response.data.data

function get<T.U> (path: string, params: T) :Promise<U> {
  return new Promise((resolve, reject) = > {
    axios
      .get(path, {
        params: params,
      })
      .then((response) = > {
        resolve(response.data.data)
      })
      .catch((error) = > {
        reject(error)
      })
  })
}
Copy the code

Day 6 Mobile payment

Server-side implementation

  1. The introduction ofstripe
npm i stripe --save
Copy the code
  1. configurationstripe
// src/utils/stripe.ts
import Stripe from 'stripe'
import { config } from '.. /config'
import { PAYMENT_TYPE } from 'quboqin-lib/lib/card'

export const stripe = new Stripe(
  config.paymentType === PAYMENT_TYPE.STRIPE_ONLINE
    ? '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'
    : 'sk_test_DU1VhfGGYrEv5EvJS2my4yhD',
  { apiVersion: '2020-08-27'},)Copy the code
  1. Initiate a payment call in the order interface

Front-end implementation

  1. inhtmlAdd to filestripeGlobal module
    <script src="https://js.stripe.com/v3/"></script>
Copy the code
  1. addstripeThe encapsulation
// scr/utils/stripe.js

// eslint-disable-next-line no-undef
export const stripe = Stripe(
  process.env.VUE_APP_ONLINE_PAYMENT === '1'
    ? 'pk_live_51HZCSNLZTTlHwkSOdYTRnFdh0AxF7JNwXShbMrKfEPzxnXLPzGz0hXJJzKxybnWngvF89FKJRXxnr2fo8zpNlZ5700Nm864NNM'
    : 'pk_test_GrY8g9brTmOaRZ6XKks0woG0'.)export const elements = stripe.elements()

const elementStyles = {
  base: {
    color: '#32325D'.fontWeight: 500.fontFamily: 'Source Code Pro, Consolas, Menlo, monospace'.fontSize: '16px'.fontSmoothing: 'antialiased'.'::placeholder': {
      color: '#CFD7DF',},':-webkit-autofill': {
      color: '#e39f48',}},invalid: {
    color: '#E25950'.'::placeholder': {
      color: '#FFCCA5',,}}}const elementClasses = {
  focus: 'focused'.empty: 'empty'.invalid: 'invalid',}// export const cardIBan = elements.create('iban', {
/ /... {
// style: elementStyles,
// classes: elementClasses,
/ /},
/ /... {
// supportedCountries: ['SEPA'],
// placeholderCountry: 'US',
// hideIcon: true,
/ /},
// })

export const cardNumber = elements.create('cardNumber', {
  style: elementStyles,
  classes: elementClasses,
})

export const cardExpiry = elements.create('cardExpiry', {
  style: elementStyles,
  classes: elementClasses,
})

export const cardCvc = elements.create('cardCvc', {
  style: elementStyles,
  classes: elementClasses,
})
Copy the code
  1. Add the development environment configuration

.env.env.development Modify axois to read configuration

// src/utils/axios.ts
const port = process.env.VUE_APP_PORT
const url = process.env.VUE_APP_BASE_URL
axios.defaults.baseURL = port ? `${url}:${process.env.VUE_APP_PORT}` : `${url}`
axios.defaults.timeout = process.env.VUE_APP_TIMEOUT
  ? +process.env.VUE_APP_TIMEOUT
  : 5000
axios.defaults.headers.post['Content-Type'] =
  'application/x-www-form-urlencoded; charset=UTF-8'
Copy the code
  1. Modify CreditCardDetail. Vue

Day 7 Continuous integration

Front-end compilation environment andHeroku.Amplify, self-builtOracle VPSThe deployment of

Deployed toHeroku

  1. createstagingproductionTwo separate apps are not the same as server deployment, both environments are inWebpackEnvironment variables are injected when packaging. Both environments need to go through CI’s compilation and packaging process, so they passPipelinePromoteIt doesn’t make sense. Here we create two separate apps: [staging-ft, production-ft]
  2. Add editing plug-ins for each APP, because it is static deployment, thanNode ServerThere is a plugin
heroku buildpacks:add heroku/nodejs
heroku buildpacks:add https://github.com/heroku/heroku-buildpack-static
Copy the code

And add static.json to the root directory of your project

{
  "root": "dist"."clean_urls": true."routes": {
    "/ * *": "index.html"}}Copy the code
  1. Bind the associated Github repository and branch in the Settings of both apps

VUE_APP_URL overrides axiOS ‘default BASE_URL at compile time and points to the corresponding Node Server. Different branches can have different values. Instead of Amplify, set NODE_ENV=production. NPM ci does not affect the module under Install devDependencies

throughAmplifyThe deployment of

createamplify.yamlFile to modify the build script
version: 0.2
backend:
  phases:
    build:
      commands:
        - '# Execute Amplify CLI with the helper script'
        - amplifyPush --simple
frontend:
  phases:
    preBuild:
      commands:
        - npm ci
    build:
      commands:
        - nvm use $VERSION_NODE_10
        - node -v
        - if [ "${AWS_BRANCH}" = "aws-staging" ]; then echo "staging branch" && npm run build:staging; elif [ "${AWS_BRANCH}" = "aws-production" ]; then echo "production branch" && npm run build:production; else echo "test branch" && npm run build:mytest; fi
  artifacts:
    baseDirectory: dist
    files:
      - '* * / *'
  cache:
    paths:
      - node_modules/**/*
Copy the code

When pushed to the appropriate branch, Amplify calls the script for compilation, packaging, and deployment

Set environment-specific variables

Here,VUE_APP_URLIs in thecompileOverride the axiOS default BASE_URL to point to the Node Server. Different branches can also have different values

  • Be careful not to addNODE_ENV=production, the NPM CI does notinstall devDependenciesUnder the module, will causenpm run buildError reported could not be foundvue-cli-service
  • VueWebpackDepending on the--mode [staging | production ]Find the corresponding.env.\*File, and declare it in theseNODE_ENV=production
createAmplifyrole

When you create the Amplify APP, it does not appear that the associated roles are automatically createdI created one manually

Deployed toOracle CentOS 8In the serverNginx

  1. Configure Nginx to support static service of multiple secondary domain names for a single port
  • Edit /etc/nginx/nginx.conf to support the same port on different static servers
server {
    listen  80;
    server_name     test.magicefire.com;
    root            /usr/share/nginx/html/test;
    location / {}
}

server {
    listen  80;
    server_name     staging.magicefire.com;
    root            /usr/share/nginx/html/staging;
    location / {}
}

server {
    listen  80;
    server_name     production.magicefire.com;
    root            /usr/share/nginx/html/production;
    location / {}
}
Copy the code

Create a corresponding directory and test HTML under the directory

  • Modify theCloudflare, add threeARecord, supportVPSIP

  • throughLet's EncryptModify thenginxhttpssupport

Installing Certbot See Node Server deployment

certbot -nginx
Copy the code

  1. Add Github Actions under.github/workflows and write the Github Actions deployment script

NODE_ENV=production; NPM ci does not install devDependencies. Leads to NPM run the build error unable to find a vue cli – service of vue Webpack – mode according to [the staging | production] find corresponding. The env. * files, Again declare NODE_ENV=production in these

  1. Add encrypted Secrets for Actions in Github repository Settings

DEPLOY_ORACLE=/usr/share/nginx/html

The back-end operating environment andHeroku/EC2The deployment of

The runtime loads configuration items based on the environment

  1. Define the environment [development, test, staging, production] with NODE_ENV in the package.json script
  2. The dotenv module is installed. The module reads the corresponding.env.[${NODE_ENV}] file in the project root directory through NODE_ENV and loads the environment variables in this environment
  3. Define different configurations by topic[cognito, Postgres, server, etc] and convert strings such as process.env.is_online_payment into different variable types
  4. Environment variables can be configured at three different levels
  • In the SHELL environment variables of the running machine, NODE_ENV and other sensitive passwords and keys are defined for the environment
  • In the.env file ending with.${NODE_ENV}
  • The configuration information is grouped by topic in the SRC /config directory
  • The rest of the code reads configuration information from SRC /config
  1. Setting NODE_ENV in a backend environment has a side effect before Typescript is compiled and packaged

** If NODE_ENV is set to production, NPM CI will not install the dependencies in devDependencies and will report an error if a package is compiled on a running EC2. So I put the package compilation in the Github Actions container, ** We have installed all our packages using the –save-dev flag. In production, if we use npm install –production these packages will be skipped.

AmplifyCreate an online user library

  1. On the front end, Amplify adds an online environment
amplify env add prod
Copy the code
  1. Check the status and deploy toAmplifyThe background
amplify status
Copy the code

amplify push
Copy the code

Enter theAWS CognitoBackstage, there will be an online oneUserPool

  1. Add foodtruck2826b8ad2826b8adCreateAuthChallenge – prod SMS privileges

  2. Change the AWS_COGNITO_USER_POOL_ID in the online configuration file

// .env.oracle
// .env.staging
// .env.production
AWS_COGNITO_USER_POOL_ID=ap-northeast-1_2ae9TN9FH
Copy the code

It mainly distinguishes the deployment of the three modules in different environments

  • Postgres database
  • pay
  • The user authentication

Modify HTTPS certificate loading

Heroku deployment

Heroku is the simplest of the three CI/CD processes we have introduced

  1. Create a Pipeline under which you can create staging and Production applications

2. Associate the corresponding repository and branch on Github in APP Settings

  • APP staging Select the Heroku-staging branch
  • APP Production selects the Heroku-Production branch
  1. Add heroku/ NodeJS compilation plug-ins for each APP
heroku login -i
heroku buildpacks:add heroku/nodejs -a staging-api-food-truck
Copy the code
  1. Set environment variables at run time

The SERVER runtime environment variable tells index.ts not to load an HTTPS SERVER, but to use an HTTP SERVER. ** Heroku’s API gateway already supports HTTPS. The backend node server is HTTP on the Intranet, so change the code to HTTP server. Otherwise, 503 error will be reported ** 5. NODE_ENV=production. APP staging can be performed at any time after the APP staging is approved. Promote enables rapid deployment. 7. View the logs on Heroku

heroku logs --tail -a staging-api-food-truck
Copy the code

AWS EC2 deployment

Set up the environment and create users and roles on AWS
CodeDeploy

We’ll be using CodeDeploy for this setup so we have to create a CodeDeploy application for our project and two deployment groups for the application. One for staging and the other for production.

  1. To create the api-server CodeDeploy application using AWS CLI, we run this on our terminal:
aws deploy create-application \
--application-name api-server \
--compute-platform Server
Copy the code
  1. Before we run the cli command to create the service role, we need to create a file with IAM specifications for the role, copy the content below into it and name it code-deploy-trust.json
{
  "Version": "2012-10-17"."Statement": [{"Sid": ""."Effect": "Allow"."Principal": {
        "Service": [
          "codedeploy.amazonaws.com"]},"Action": "sts:AssumeRole"}}]Copy the code
  1. We can now create the role by running:
aws iam create-role \
--role-name CodeDeployServiceRole \
--assume-role-policy-document file://code-deploy-trust.json
Copy the code
  1. After the role is created we attach the AWSCodeDeployRole policy to the role
aws iam attach-role-policy \
--role-name CodeDeployServiceRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole
Copy the code
  1. To create a deployment group we would be needing the service role ARN.
aws iam get-role \
--role-name CodeDeployServiceRole \
--query "Role.Arn" \
--output text
Copy the code

The ARN should look something like arn:aws:iam::403593870368:role/CodeDeployServiceRole 6. Let’s go on to create a deployment group for the staging and production environments.

aws deploy create-deployment-group \
--application-name api-server \
--deployment-group-name staging \
--service-role-arn arn:aws:iam::403593870368:role/CodeDeployServiceRole \
--ec2-tag-filters Key=Name,Value=staging,Type=KEY_AND_VALUE
Copy the code
aws deploy create-deployment-group \
--application-name api-server \
--deployment-group-name production \
--service-role-arn arn:aws:iam::403593870368:role/CodeDeployServiceRole \
--ec2-tag-filters Key=Name,Value=production,Type=KEY_AND_VALUE
Copy the code

Go to Console -> Code Deploy to confirm

Create a S3 Bucket

Create an S3 Bucket named Node-koa2-typescript

aws s3api create-bucket --bucket node-koa2-typescript --region ap-northeast-1
Copy the code

Create and Launch EC2 instance

For a complete demonstration, you should create two EC2 instances, staging and Production. To save resources, only one instance will be created here

  1. Create a role EC2RoleFetchS3 that has access to S3

2. In this article, we will be selecting the Amazon Linux 2 AMI (HVM), SSD Volume Type.

  • Bind the role created above and enable ports 80/22/3001/3002

  • Add a tag where key is Name and Value is production

  • Import the public key for SSH remote login

  • Log in to the EC2 instance remotely over SSH to install CodeDeploy Agent

For details, see CodeDeploy Agent 3. Use SSH to install the Node.js operating environment

  • Install Node.js from NVM
The curl - o - https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash source ~ /. Following NVM list - remote nvm install 14Copy the code
  • Install the PM2 management node process
npm i pm2 -g

Copy the code
  • Create file.config.js in the root directory of your project
module.exports = {
  apps: [{script: './dist/index.js',},],}Copy the code
  1. In the EC2 instance ec2-user account, set the environment variable and edit ~/.bash_profile
export NODE_ENV=production
export SERVER=AWS
Copy the code

NODE_ENV and environment variables with sensitive information are placed here, and SERVER=AWS is just a demonstration

Create user groups and permissions for the Github Actions deployment script
  1. Create a CodeDeployGroup user group in IAM and grant AmazonS3FullAccess and AWSCodeDeployFullAccess permissions
  2. Add a dev user to CodeDeployGroup to record the Access Key ID and Secret Access Key

Write the Github Actions script
  1. Create the. Github /workflows/deploy-ec2.yaml file in the root directory of the project

The purpose of deploy-ec2.yaml is to trigger compilation, packaging, and staging when the modified code is submitted to AWS -staging or AWS – Production. Upload to S3’s Node-koa2-typescript bucket and then trigger CodeDeploy to complete the subsequent deployment. So the Github Action is the role of CI and the CodeDeploy behind it is the role of CD. Add Environment secrets to Github project. Add Environment secrets to dev user’s Access key ID and Secret Access key

Add AppSpec.yml and related scripts

After CodeDeploy gets the latest package from the S3 Node-koa2-typescript bucket, upload it to the EC2 instance and decompress it to the appropriate directory, where we specify /home/ec2-user-/ api-server. CodeDeploy Agent will find the appSpec. yml file in this directory to execute the Hook scripts at different stages

version: 0.0
os: linux
files:
  - source: .
    destination: /home/ec2-user/api-server
hooks:
  AfterInstall:
    - location: aws-ec2-deploy-scripts/after-install.sh
      timeout: 300
      runas: ec2-user
  ApplicationStart:
    - location: aws-ec2-deploy-scripts/application-start.sh
      timeout: 300
      runas: ec2-user
Copy the code

Aws -ec2-deploy-scripts/application-start.sh Starts the Node.js service

#! /usr/bin/env bash
source /home/ec2-user/.bash_profile
cd /home/ec2-user/api-server/
pm2 delete $NODE_ENV
pm2 start ecosystem.config.js --name $NODE_ENV
Copy the code
Install a free domain name certificate in an EC2 instanceCertificate automation: Let’s Encrypt with Certbot on Amazon Linux 2
  1. Go to Cloudflare and add A record pointing to this EC2 instance, specifying that the secondary domain name is AWS-API

2. Install and configure the Apache server for certificate authentication 3

sudo certbot -d aws-api.magicefire.com
Copy the code

According to the instructions, the final certificate generated in/etc/letsencrypt/live/aws-api.magicefire.com/ 4 directory. Since the ec2-user account is used to start the Node service and the certificate is created with the root permission, go to /etc/letsencrypt and add the read permission to the live and archive directories for other users

sudo -i
cd /etc/letsencrypt
chmod -R 755 live/
chmod -R 755 archive/
Copy the code
  1. Configure automated certificate renewal
Deployment and validation
  1. If there is no AWS – Production branch, create the branch first, switch to the branch, merge the modified code and push it to Github
git checkout -b aws-production
git merge main
git push orign 
Copy the code
  1. Trigger dead simple Actions

  1. After compiling, packaging, and uploading to S3, CodeDeploy is triggered

  1. Check in your browser when you’re done

Or use curl to confirm the command line

curl https://aws-api.magicefire.com:3002/api/v1/health
Copy the code

  1. Because we did not create a staging instance of EC2, CodeDeploy will prompt the following error if we push to the AWS -staging branch

Process review

Postgres VPS and Heroku deployment [Server]

Deploy Postgres on Heroku

  1. Provisioning Heroku Postgres
heroku addons
heroku addons:create heroku-postgresql:hobby-dev
Copy the code
  1. Sharing Heroku Postgres between applications
heroku addons:attach my-originating-app::DATABASE --app staging-api-node-server
Copy the code
  1. Import goods into online table
npm run import-goods-heroku-postgre
Copy the code

Deploy Postgres on VPS

side

supportSwagger