全栈开发之路
全栈开发之路
Backed Develop
环境配置
创建文件夹
创建api文件夹
命令
npm init
初始化安装Express,使用
npm install express
安装依赖,
npm install -D typescript tsx @types/express
创建
tsconfig.json
文件(代码如下)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20{
"compilerOptions": {
"module": "ESNext", // Use ESNext for ESM
"target": "ES2020", // Target modern ECMAScript versions
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"outDir": "./dist", // Output directory for compiled files
"strict": true, // Enable strict type-checking options
"skipLibCheck": true, // Skip type checking of declaration files
"resolveJsonModule": true, // Include JSON imports
"forceConsistentCasingInFileNames": true,
"noEmit": false, // Allow emitting output
"isolatedModules": true, // Required for using ESM modules
"baseUrl": ".", // Allow absolute imports relative to project root
"paths": {
"*": ["node_modules/*"]
}
}
}
编写hello world示例程序
创建src目录,创建index.ts文件
(代码如下)
1
2
3
4
5
6
7
8
9
10
11
12import express from 'express';
const port = 3000;
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})这时候尝试运行
node src/index.ts
会发生import和type错误1
2
3
4"name": "ecommerce-api",
"version": "1.0.0",
"main": "index.js",
"type": "module", //添加这一行,可以解决import问题然后运行命令
node --import=tsx src/index.ts
解决type问题修改package.json文件,之后运行使用
npm run dev
即可1
2
3
4
5
6"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "node --import=tsx --watch --env-file=.env src/index.ts",
"build": "tsc"
},
//添加dev命令,同时添加--watch检查变化,创建.env文件,添加环境变量
配置git
返回上一级文件,运行
git init
创建
.gitignore
文件,配置一些基本信息1
2
3node_modules
dist
.env之后就和正常的上传github的过程是一样的(我在从0到1里也有写)
安装vscode插件
- thunder client,用于测试api,也可以使用postman,看个人吧
路由和控制概念
通过发送请求,返回信息
1
2
3
4
5
6
7
8
9
10
11
12app.get('/products', (req, res) => {
res.send('the list of products');
})
app.get('/products/:id', (req, res) => {
console.log(req.params); //id
res.send('A product');
})
app.post('/products', (req, res) => {
res.send('New product created');
})使用路由之后
1
2
3
4
5
6
7
8
9import express, { Router } from 'express';
const router = Router();
router.get('/', (req, res) => {
res.send('the list of products');
})
app.use('/products', router);将路由放置到另外的地方
在src目录下创建文件夹routes/products/index.ts
将上面部分代码移动进入其中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import { Router } from 'express';
const router = Router();
router.get('/', (req, res) => {
res.send('the list of products');
})
router.get('/:id', (req, res) => {
console.log(req.params);
res.send('A product');
})
router.post('/', (req, res) => {
res.send('New product created');
})
export default router;修改原来的index.ts
1
2
3import productsRoutes from './routes/products/index';
app.use('/products', productsRoutes);
添加控制组件
创建文件routes/products/productsController.ts
1
2
3
4
5
6import { Request, Response } from "express";
export function listProducts (req: Request, res: Response) {
res.send('the list of products qaq');
}
//剩下部分类似...修改routes/products/index.ts文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import {
listProducts,
getProductById,
createProduct,
updateProduct,
deleteProduct,
} from './productsController';
const router = Router();
router.get('/', listProducts);
router.get('/:id', getProductById);
router.post('/', createProduct);
router.put('/:id', updateProduct);
router.delete('/:id', deleteProduct);
尝试传送数据
在index.ts文件中添加
1
2
3import express, {json} from 'express';
app.use(json());//将req解析为json可以在productsController.ts文件中添加代码查看
1
2
3
4export function createProduct (req: Request, res: Response) {
console.log(req.body); //添加这个
res.send('createProduct');
}
添加中间件?
1
2
3
4
5import express, { json, urlencoded } from 'express';
app.use(urlencoded({ extended: false }));
//urlencoded 中间件负责将 application/x-www-form-urlencoded 编码的数据解析为 JavaScript 对象。
//extended: false 限制了解析的数据结构(只能是字符串或简单的数组对象),而 extended: true 允许解析更复杂的嵌套数据结构。
建立数据库以及ORM工具
这里使用Genezio申请了一个数据库使用
这里打算使用drizzle作为ORM交互工具
然后…然后请去对应的官网完成安装(^▽^)
创建文件src/db/index.ts (example)
1
2
3
4
5
6
7
8import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL!,
});
export const db = drizzle({ client: pool });然后创建schema,这里我创建productsSchema.ts (example)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import {
integer,
pgTable,
varchar,
text,
doublePrecision
} from "drizzle-orm/pg-core";
export const productsTable = pgTable("products", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
name: varchar({ length: 255 }).notNull(),
description: text(),
image: varchar({ length: 255 }),
price: doublePrecision().notNull(),
});(对了,这里移除了
import 'dotenv/config
是因为在package.json
里面已经添加了–env-file=.env)然后按照文档创建
drizzle.config.ts
文件1
2
3
4
5
6
7
8
9
10
11
12import { defineConfig } from 'drizzle-kit';
export default defineConfig({
out: './drizzle',
schema: ['./src/db/productsSchema.ts'],//这里是string []
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
verbose: true,
strict: true,//more log
});然后按照官方文档里面的generate,migrate进行使用(。。喵的我连接不上,气死我了沟槽的闭关锁国)——现在在使用Neon,Thanks♪(・ω・)ノ
然后按照文档,编写需要的API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import { Request, Response } from "express";
import { db } from "../../db/index";
import { productsTable } from "../../db/productsSchema";
import { eq } from "drizzle-orm";
export async function listProducts (req: Request, res: Response) {
try {
const products = await db
.select()
.from(productsTable);
res.json(products);
} catch(err) {
res.status(500).send(err);
}
}//示例
信息验证检查
有像zod这样的工具:npm intall zod
这里选择创建文件**/src/middlewares/validationMiddleware.ts**
1 | import { Request, Response, NextFunction } from 'express'; |
然后在routes的index.ts进行简单的测试
1 | import { validateData } from '../../middlewares/validationMiddleware'; |
然后…接下来这部分就有点confused了,主要应该是让req里面不管传递什么,都只保留需要的部分数据部分。
首先创建src/types/express/index.d.ts文件处理类型检查
1 | export {}; |
然后在上面的validationMiddleware.ts做修改(这里得先安装lodash)
1 | import _ from 'lodash'; |
然后在productSchema.ts文件中添加
1 | export const createProductSchema = createInsertSchema(productsTable).omit({ |
然后同上所述,在index.ts中引入中间件
1 | router.post('/', validateData(createProductSchema), createProduct); |
最后记得将productController.ts文件中的req修改为req.cleanBody
Auth验证和Token检查使用
首先,如上所述,需要创建一个user表(usersSchema.ts)
1 | export const usersTable = pgTable("users", { |
然后修改drizzle.config.ts文件,使用对应的schema
1 | schema: ['./src/db/productsSchema.ts', './src/db/usersSchema.ts'], |
然后在routes文件夹中创建auth/index.ts,同上面product作用类似
这里使用了两个library:bcryptjs && jsonwebtoken(请安装)
ok,举一个例子
1 | const router = Router(); |
然后在middlewares文件夹中新建一个中间件authMiddleware.ts
1 | import { Request, Response, NextFunction } from 'express'; |
最后再回到products的index.ts,添加验证:(example)
1 | router.post('/', verifyToken, verifySeller, validateData(createProductSchema), createProduct); |
以上过程可以自己发送请求测试
————来自之后癫狂的我:喵的,前面没发现到这里就不行了,没验证,导致后面全部都出问题,记住token要放在Auth那里,而不是放在header里,前面因为懒得检查导致了这么多的错误!!以后真别懒了!!fuck
{
- JWT(JSON Web Token)概述
JWT 是一种用于安全传输信息的紧凑且自包含的格式。它通常用于在客户端和服务器之间传递认证信息,特别是用于 身份验证(Authentication)和 授权(Authorization)场景。
JWT 通常由三部分组成:
- Header:头部,指定签名算法(如 HMAC SHA256 或 RSA)。
- Payload:有效载荷,存储传递的数据(如用户 ID、角色等)。
- Signature:签名,用于验证 token 是否被篡改。
decoded
是什么?
在你的代码中,jwt.verify(token, 'your-secret')
这行代码的作用是 验证和解码 传入的 JWT Token。具体来说,decoded
是经过 jwt.verify
解码后的结果,包含了 JWT 中的有效载荷部分。
- 如果 token 合法且没有被篡改,
jwt.verify
会返回一个包含 payload 数据的对象,通常是用户身份的相关信息,比如userId
、role
等。 - 如果 token 无效或被篡改,
jwt.verify
会抛出错误。
假设你的 JWT 的 payload 部分存储了以下信息:
1 | json{ |
当你调用 jwt.verify(token, 'your-secret')
后,decoded
就会是:
1 | json{ |
req.userId = decoded.userId
和req.role = decoded.role
的作用
这两行代码的目的是将 JWT 中的有效载荷信息(即 userId
和 role
) 附加到请求对象 req
上,这样在后续的中间件或路由处理函数中,你可以方便地访问这些信息。
- **
req.userId = decoded.userId;
**:将decoded
中的userId
存储到req.userId
,意味着你可以在其他地方通过req.userId
获取当前请求用户的 ID。 - **
req.role = decoded.role;
**:将decoded
中的role
存储到req.role
,意味着你可以在其他地方通过req.role
获取当前请求用户的角色(如管理员、商家等)。
}
构建dist && deploy
众所周知,只需要输入npm run build
就可以了
然后在命令行输入node dist/src/index.js
尝试运行之后,会发现——诶,不行
没错,我们要把全部文件中的那些引用文件添加”.js”后缀
然后再构建之后再使用命令,会发现——诶,还是不行
其实是因为我们没使用环境变量(^▽^)
所以要输入node --env-file=.env dist/src/index.js
之后使用的Genezio进行项目的部署(请查看文档docs)
这里我看视频里没有写,但是我的错误日记里写了找不到dist里面的export handler,因为我添加完代码之后没有再重新build。。很合理,但是不知道为什么那个youtube视频里面没有build也行??就很奇怪,总而言之,有错误日记真是好事情啊~
之后如果在本地运行会出现Completed running 'src/index.ts'
这种情况QAQ(之前没搞清楚的时候着实被折磨了一下)
之后如果要运行的话需要修改package.json里的scripts为:
1 | "dev": "NODE_ENV=dev node --import=tsx --watch --env-file=.env src/index.ts", |
因为部署的时候修改了index.ts为:
1 | if (process.env.NODE_ENV === "dev") { |
然后…我们就会发现,还是不行?
于是,万能的AI,快救救我吧——于是,AI降临了,并传下神谕:
{
这个错误通常是因为在 Windows 系统中使用 NODE_ENV=dev
的方式设置环境变量时,命令行无法识别这种语法。在 Windows 环境中,设置环境变量的语法与类 Unix 系统(如 Linux、macOS)不同,因此会导致 'NODE_ENV' 不是内部或外部命令
错误。
解决办法:
1. 使用 cross-env
模块
cross-env
是一个用于跨平台设置环境变量的工具,它可以使得在 Windows 和 Linux/macOS 上的命令都能正常工作。
步骤 1:安装
cross-env
你可以通过 npm 安装
cross-env
:1
bashnpm install cross-env --save-dev
步骤 2:修改
package.json
中的scripts
在
package.json
文件中的dev
脚本部分,使用cross-env
来设置环境变量:1
2
3json"scripts": {
"dev": "cross-env NODE_ENV=dev node --import=tsx --watch --env-file=.env src/index.ts"
}这样,
cross-env
会确保在所有操作系统上正确地设置NODE_ENV
环境变量。
}
后端工程暂时到这里告一段落吧QAQ
(PS:感觉数据库的设计还是有点麻烦的)
MOBILE-APP
环境配置
- 到根目录下运行
npx create-expo-app@latest --template
命令 - 然后这里选择的是blank typescript
- 然后请按照expo官方文档中的expo router install的引导页面进行环境安装配置
- (QAQ,然后不知道为啥没有代码补全,所以快速安装了,然后遇到一个离谱问题:
File 'expo-module-scripts/tsconfig.base' not found.ts
,然后找了半天。。发现重启编译器就行, 好气) - 学到一个新东西,expo go还可以全世界连接hh
安装使用gluestack-ui(!!!)
按照官方文档肯定可以简简单单完——完蛋,不出所料,应该——是要出问题了。
喵的,不知道为什么添加组件的时候访问超时无法连接。但是我觉得这个UI库应该还挺重要的,所以还得想想其它的办法才行。
OK,所谓的科学就是不断试验学习到红温然后解决问题,根据如下这篇文章的内容解决了这个问题
太好了!O(∩_∩)O
React-native简单介绍?
(不知道为什么他用了好多组件啊,我个人觉得这些简单的功能有tailwindcss应该都挺简单就可以写出来了吧)
目前RootLayout如下所示
1 | import "@/global.css"; |
应该很显然可以知道,stack用于显示上面那个状态栏的名字,当然也可以直接在页面中使用,如这里在/product/[id]中使用 <Stack.Screen options={{ title: product.name }} />
fetch数据简单介绍?
因为已经有backed,所以相对来说已经挺简单的了。创建一个api文件夹,存放需要的api
1 | const API_URL = process.env.EXPO_PUBLIC_API_URL; |
使用时大概如下:
1 | const [products, setProducts] = useState(); |
补充:使用了TanStackQuery
TanStack Query(以前叫做 React Query)是一个强大的数据获取、缓存和同步管理库,特别适用于 React 应用中。它帮助开发者轻松地管理应用中的数据流,包括服务器端的数据获取、缓存管理、背景数据同步和请求的重试等功能。
主要功能
- 数据获取与缓存:
- TanStack Query 使得获取数据变得简单,通过定义
queries
和mutations
,你可以轻松地发起 HTTP 请求并缓存数据,避免不必要的重复请求,提高性能。 - 它会自动缓存成功获取的数据,缓存中的数据会在组件重新渲染时复用,从而减少网络请求和提升应用性能。
- TanStack Query 使得获取数据变得简单,通过定义
- 自动后台数据同步:
- TanStack Query 会自动在后台重新获取数据,确保你的应用始终显示最新的数据。它会基于某些条件(如窗口重新聚焦、定时重新请求等)重新加载数据。
- 自动重试与失败处理:
- 当请求失败时,TanStack Query 可以自动重试请求,支持定制化的重试次数和重试间隔。
- 状态管理:
- 它简化了从 API 获取数据时的状态管理,比如加载中、成功、失败等状态,开发者可以直接通过 TanStack Query 提供的钩子来管理这些状态,而不需要手动管理复杂的加载和错误状态。
- 分页与无限加载:
- TanStack Query 支持分页和无限加载模式,适用于需要按页加载数据的场景,如数据表格或无限滚动列表。
- Mutation 支持:
- 除了查询数据,TanStack Query 还支持对数据的变更(如创建、更新和删除操作)。这些操作被称为“mutations”,它提供了非常便捷的钩子来处理这些操作的成功、失败以及相关的状态管理。
( 血的教训孩子们(o(╥﹏╥)o))
我在后端代码里写了,注册邮箱必须唯一,然后我在前端测试的时候忘记了,结果各种找错问题,折腾了半天,突然灵感一闪——艹,我是不是用了以及用过的邮箱。这真的是经典问题了,之前我开发后端的时候就遇到过这个问题了,哎,以后还是在后端那边打印点提醒吧。吐了,呕
补充:Zustand
Zustand 是一个轻量级、简单且强大的状态管理库,特别适合 React 应用程序。它提供了一种简洁的 API 用于管理应用中的全局状态,并且不需要太多的样板代码。Zustand 的设计目标是简单、直观,并且尽可能少的抽象。
核心特点:
- 简洁易用:
- 你只需要定义一个状态(store)和一些操作(actions),就可以开始管理状态。
- 没有复杂的概念,如 reducers、actions、dispatch 等,使用起来非常直观。
- 无依赖性:
- Zustand 没有外部依赖,除了 React。它是纯粹的 JavaScript,因此不需要额外的库或工具来运行。
- 自动订阅:
- Zustand 内部自动管理组件的订阅,组件会在状态变化时自动重新渲染,确保状态与 UI 的同步。
- 支持持久化:
- Zustand 可以与本地存储结合使用,支持持久化状态,可以将状态存储到
localStorage
或sessionStorage
。
- Zustand 可以与本地存储结合使用,支持持久化状态,可以将状态存储到
- 极简的 API:
- Zustand 提供了非常简单的 API,只需要一个函数
create
来创建一个 store。
- Zustand 提供了非常简单的 API,只需要一个函数
基本用法
安装: 如果你使用 npm 或 yarn,你可以通过以下命令安装 Zustand:
1
bashnpm install zustand
或者:
1
bashyarn add zustand
创建 Store: 使用
create
函数来创建一个状态管理 store。你可以在 store 中定义状态和方法(actions)来改变状态。1
2
3
4
5
6
7
8javascriptimport create from 'zustand';
// 创建一个 store
const useStore = create((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
decrease: () => set((state) => ({ count: state.count - 1 })),
}));useStore
是一个 hook,可以在 React 组件中使用。set
用来更新状态,state
表示当前的状态。- 在
set
里面定义的方法会被用作操作状态(如increase
和decrease
)。
使用 Store: 在 React 组件中,你可以通过
useStore
hook 获取状态和操作:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15javascriptimport React from 'react';
import { useStore } from './store';
const Counter = () => {
const { count, increase, decrease } = useStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increase}>Increase</button>
<button onClick={decrease}>Decrease</button>
</div>
);
};
export default Counter;useStore
返回的对象包含了状态(如count
)和操作(如increase
和decrease
)。- 当调用
increase
或decrease
时,状态会被更新,组件会自动重新渲染。
自定义状态和方法: 你可以根据需要自定义状态和方法。Zustand 允许你非常灵活地组织 store。
1
2
3
4
5javascriptconst useStore = create((set) => ({
user: null,
setUser: (user) => set({ user }),
clearUser: () => set({ user: null }),
}));- 在这个例子中,
user
是一个状态,setUser
和clearUser
是方法,用来设置和清除用户数据。
- 在这个例子中,
其他特点:
派发异步操作: Zustand 也支持异步操作。例如,你可以在方法中执行异步请求:
1
2
3
4
5
6
7
8javascriptconst useStore = create((set) => ({
data: null,
fetchData: async () => {
const response = await fetch('/api/data');
const data = await response.json();
set({ data });
},
}));持久化状态: Zustand 提供了简单的持久化机制。你可以通过
persist
中间件将状态存储到localStorage
或sessionStorage
中:1
2
3
4
5
6
7
8
9
10
11
12
13
14javascriptimport create from 'zustand';
import { persist } from 'zustand/middleware';
const useStore = create(
persist(
(set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'counter-storage', // 持久化到 localStorage 中的名字
}
)
);