介绍
谢谢您的等待!这是上篇动手文章的第二部分。
这次,我们将在前端(NextJS),Nginx服务器和Docker周围启动设置。
我写了这篇文章,希望对于那些从现在开始尝试使用该技术堆栈的人来说,它将是一个类似于模板的代码。
现在就开始吧!
先决条件
- Docker已安装。 (安装Dockerhub更容易)
- 已安装Node(为了方便使用软件包,建议使用v12.0.0。如果安装了nvm,则可以方便地切换版本。)
- 如果您想动手操作,请阅读服务器端版本。
目标观众
- 那些已经独立研究了Docker,React,NodeJS等的人,但是想全面地学习一切
- 暂时,那些只想使用Typescript开发全栈应用程序的人
- NextJS?NestJS?Docker,但还没有使用过?想要使用它的人
单击此处查看完整的代码
主要故事
上一次,我主要在服务器端(NestJS)上实现了它。使用称为NestJS的框架的特殊标记体系结构,我将与连接代码相对应的代码写入数据库和HTTP请求(GET和POST)。
这次我们使用NextJS从前端进行开发!
在此之前,作为最后一次回顾,我将发布该应用程序的配置图!
应用配置
- 要点(1):通过将nginx服务器夹在浏览器和系统之间,它被赋予了代理服务器/路由器的角色。如果在request参数中仅指定" /",则将其分配给NextJS,如果在参数中指定" / api",则将其分配给NestJS。
- 要点(2):在Docker容器中启动应用程序。这样可以进行与环境无关的开发。
让我们用NextJS实现前端!
创建一个项目
首先,前端!点击以下命令创建一个项目!
单击此处以获取NextJS文档
1 2 3 4 | # npm派はこちら npx create-next-app # yarn派はこちら yarn create next-app |
然后,将如下创建项目!
创建typescript配置文件
但是,这次我想使用Typescript,所以让我们在根目录中创建一个
1 2 3 4 5 6 | touch tsconfig.json # yarn run devで試しに実行してみると、以下のdependenciesをインストールする ように怒られるのでしておく yarn add --dev typescript @types/react @types/node |
如下修改tsconfig.json文件
tsconfig.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | { "compilerOptions": { "sourceMap": true, "noImplicitAny": true, "module": "esnext", "target": "es6", "jsx": "preserve", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "baseUrl": "./", "skipLibCheck": true, "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "resolveJsonModule": true, "isolatedModules": true, "moduleResolution": "node" }, "include": [ "pages/**/*", ], "exclude": [ "node_modules" ] } |
这一次,我设置为递归检查
NextJS项目现在支持Typescript。之后,让我们将
同时设置ESLINT和Prettier
我想事先使开发代码更具可读性... ????
我认为有个人喜好,但让我们设置最低设置!
我使用VS Code作为编辑器,因此将包括ESLint和Prettier插件。
prettierrc.js
1 2 3 4 5 6 7 8 | module.exports = { semi: true, trailingComma: 'es5', singleQuote: true, printWidth: 100, tabWidth: 2, useTabs: false, } |
eslintrc.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | module.exports = { overrides: [ { files: ['*.ts', '*.tsx'], parserOptions: { project: ['./tsconfig.json'], tsconfigRootDir: __dirname, sourceType: 'module', }, }, ], extends: [ 'eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/eslint-recommended', 'plugin:prettier/recommended', 'prettier/@typescript-eslint', ], plugins: ['@typescript-eslint', 'react', 'prettier'], parser: '@typescript-eslint/parser', env: { browser: true, node: true, es6: true, }, parserOptions: { sourceType: 'module', ecmaFeatures: { jsx: true, }, }, rules: { 'react/prop-types': 'off', 'react/react-in-jsx-scope': 'off', '@typescript-eslint/no-explicit-any': 'off', 'prettier/prettier': 'error', }, settings: { react: { version: 'detect', }, }, }; |
故障排除:如果ESLint或Prettier无法正常工作,请注意,在许多情况下,package.json没有必需的依赖项,或者在VS Code设置中"保存时格式化"为假。
固定页面文件
这次,我故意简化了应用程序的配置。因此,只需修改
_app.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import React from 'react'; import type { AppProps /*, AppContext */ } from 'next/app'; import '../styles/global.scss'; function MyApp({ Component, pageProps }: AppProps): JSX.Element { return <Component {...pageProps} />; } // Only uncomment this method if you have blocking data requirements for // every single page in your application. This disables the ability to // perform automatic static optimization, causing every page in your app to // be server-side rendered. // // MyApp.getInitialProps = async (appContext: AppContext) => { // // calls page's `getInitialProps` and fills `appProps.pageProps` // const appProps = await App.getInitialProps(appContext); // return { ...appProps } // } export default MyApp; |
index.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | import Head from 'next/head'; import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { MovieListType } from '../interface/movie'; export default function Home(): React.ReactElement { const [movieName, setMovieName] = useState<string>(''); const [movieList, setMovieList] = useState<Array<MovieListType>>([]); useEffect(() => { console.log(movieList); }, [movieList]); useEffect(() => { let tmp: any = ''; const fetchMovieList = async (): Promise<void> => { const { data } = await axios.get('/api/movielist'); tmp = data; setMovieList(tmp); }; fetchMovieList(); }, []); function onMovieNameInput(event: React.ChangeEvent<HTMLInputElement>) { const inputValue = (event.target as HTMLInputElement).value; setMovieName(inputValue); } async function onClickWatchLater() { await axios.post('/api/movielist', { movieName, }); const { data } = await axios.get('/api/movielist'); setMovieList(data); } return ( <div> <Head> I Theater <link rel="icon" href="/favicon.ico" /> <link rel="preconnect" href="https://fonts.gstatic.com" /> <link href="https://fonts.googleapis.com/css2?family=Unlock&display=swap" rel="stylesheet" /> </Head> <div className="wrapper"> <div className="search"> <div> <h1 className="title">ITheater</h1> </div> <div> <input className="input u-margin-bottom-medium" value={movieName} onChange={onMovieNameInput} /> </div> <div> <button className="btn" onClick={onClickWatchLater}> Watch Later! </button> </div> </div> <div> {movieList.map((el: MovieListType, index: number) => { if (movieList.length === 0) { return <div></div>; } else { return ( <div key={index} className="result result__element"> <div className="result__row--number">{el.id}</div> <div className="result__row--border"></div> <div className="result__row--title">{el.movieName}</div> </div> ); } })} </div> </div> </div> ); } |
点是,注册新电影的名称时,请求目标的路径名称以" / api"开头。这是识别您稍后将实现的nginx服务器所需的代码!
接下来,让我们将接口定义为类型安全。基本上,它应该与服务器端实体类匹配。
movielist.ts
1 2 3 4 | export type MovieListType = { id: number; movieName: string; }; |
CSS设置
SCSS / SASS(CSS编译器)用于CSS文件。这次,我们采用" BEM"作为前端的设计符号。
该体系结构使用" 7-1"模式。
从此链接下载源代码并替换样式文件夹。
这样就完成了前端实现!
Nginx服务器实现
现在,假设您要开始实现nginx?
尽管我对
充满热情,但是nginx的设置仅涉及到w
真正基本的部分。
好吧,确切地说,我只能用w
触摸它
基本上,您充当中介程序,根据URI的路径决定是向客户端(∴NextJS)还是向服务器(∴NestJS)发送请求。
我想用一篇文章来更深入地研究nginx,但是root函数对于这个应用程序来说已经足够了...?,?????????????
让我们再来看一下
,在根目录中创建一个
default.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # NextJS upstream client { # ここでいう、clientは後ほど作成するdocker-compose.ymlで定義しているエンドポイントです。 server client:3000; } # NestJS upstream api { # ここでいう、apiは後ほど作成するdocker-compose.ymlで定義しているエンドポイントです。 server api:5000; } server { listen 80; # ここで振り分けのルールを定義します location / { proxy_pass http://client; } location /api { rewrite /api/(.*) /$1 break; proxy_pass http://api; } } |
Docker设置
终于结束了。将每个Dockerfile放置在指定位置!
1 2 3 4 5 6 7 8 9 10 11 12 13 | client/ │ ├ ... │ └ Dockerfile.dev │ nginx/ │ ├ default.conf │ └ Dockerfile.dev │ server/ │ ├ ... │ └ Dockerfile.dev │ └ docker-compose.yml |
它不是用于生产的Dockerfile,因此将
-
/client/Dockerfile.dev
1 2 3 4 5 6 | FROM node:alpine WORKDIR /app COPY ./package.json ./ RUN yarn COPY . . CMD ["yarn", "dev"] |
-
/nginx/Dockerfile.dev
1 2 | FROM nginx COPY default.conf /etc/nginx/conf.d/default.conf |
-
/server/Dockerfile.dev
1 2 3 4 5 6 | FROM node:alpine WORKDIR /app COPY ./package.json ./ RUN yarn COPY . . CMD ["yarn", "start:dev"] |
点(1):节点:alpine用于客户端和服务器。由于已在该映像中安装了节点,因此在使用节点应用程序时具有优势。
要点(2):在客户端和服务器的Dockerfile中分两步执行COPY。
1 2 3 | COPY ./package.json ./ RUN yarn COPY . . |
这是为了防止您在修改主机上的代码时一一重装(学习)软件包。您可以减少开发成本。
-
docker-compose.yml
docker-compose.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | version: "3.9" services: postgres: image: "postgres:latest" environment: - POSTGRES_PASSWORD=postgres_password client: build: context: ./client dockerfile: Dockerfile.dev volumes: - /app/node_modules - ./client:/app api: build: context: ./server dockerfile: Dockerfile.dev volumes: - /app/node_modules - ./server:/app environment: - PGUSER=postgres - PGHOST=postgres - PGDATABASE=postgres - PGPASSWORD=postgres_password - PGPORT=5432 nginx: depends_on: - client - api restart: always build: context: ./nginx dockerfile: Dockerfile.dev ports: - "3050:80" |
点(1):在客户端和api容器中设置了volumes值。通过将文件挂载在Docker的主机路径上,修改后的代码将在保存时自动更新。
1 2 3 | volumes: - /app/node_modules - ホストパス:/app |
点(2):预先在api环境变量中注册postgres设置所需的值。这将允许nest.js(服务器端)从环境变量获取所需的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 | api: build: context: ./server dockerfile: Dockerfile.dev volumes: - /app/node_modules - ./server:/app environment: - PGUSER=postgres - PGHOST=postgres - PGDATABASE=postgres - PGPASSWORD=postgres_password - PGPORT=5432 |
在
服务器端,您可以像
点③:将nginx与客户端和api相关联。
1 2 3 4 5 | nginx: depends_on: - client - api restart: always |
通过指定
让我们开始吧!
感谢所有阅读本文的人。谢谢你!
最后,在命运的时候...
让我们运行它!
1 2 3 4 5 | docker-compose up --build # 一回でnginxが起動できない場合があります。その場合は、もう一度以下のコマンドを叩いてください! docker-compose down && docker-compose up |
如果成功,您应该可以注册电影!
在最后
到目前为止与我们在一起的每个人!感谢你的努力工作!
您现在是具有
的全职工??程师
这次写了这个博客,我觉得我加深了对这一领域的了解。
我希望这篇博客文章能为那些正在考虑使用NextJS,NestJS,Typescript,Docker等进行开发的人提供帮助。
老实说,NestJS仍然很深。它还支持最热门的技术堆栈,例如GraphQL,MicroService,Prisma等。
我想写一篇文章,深入探讨。
这是一个动手系列,但是下一次我将使用Kubernetes撰写有关该版本的文章。老实说,我认为这比Docker更实用!
下次在
见!