vercel nodejs项目模板
nodejs 项目模板
初始化项目
cd nodejs-vercel-template
npm init -y创建目录结构
touch vercel.json
touch .env
touch .env.example
touch .gitignore
touch README.md
mkdir api
mkdir frontend
mkdir public
touch api/index.js编辑package.json文件
{
"name": "vercel-node-vue-bizhi", // 项目名称
"version": "1.0.0", // 项目版本
"description": "A node-vue-bizhi project", // 项目描述
"scripts": {
"dev": "npm run dev:backend & npm run dev:frontend", // 开发模式
"dev:backend": "nodemon api/index.js", // 后端开发模式
"dev:frontend": "cd frontend && npm run dev", // 前端开发模式
"build": "cd frontend && npm install && npm run build", // 构建前端
"start": "node api/index.js" // 启动后端
},
"keywords": [
"nodejs",
"vue",
"vercel",
"supabase"
],
"author": "WenYan",
"license": "MIT",
"dependencies": { // 项目依赖
"@supabase/supabase-js": "^2.39.3",
"cors": "^2.8.5",
"dotenv": "^16.4.1",
"express": "^4.18.2"
},
"devDependencies": { // 开发依赖
"nodemon": "^3.0.3"
}
}安装依赖
npm install配置api/index.js
修改index.js,引入express,并设置api和路由
require('dotenv').config(); // 加载环境变量
const express = require('express'); // 引入express
const cors = require('cors'); // 引入cors
const testRouter = require('./routers/test');
const app = express();
const PORT = process.env.PORT || 3000;
// 设置中间件 middleware
// - cors
const corsOptions = {
origin: function (origin, callback) {
// 允许列表
const allowedOrigins = [
/\.vercel\.app$/, // vercel域名
process.env.FRONTEND_URL, //自定义url
].filter(Boolean); // 过滤掉undefined
// 开发环境允许所有
if (process.env.NODE_ENV === 'development') {
callback(null, true);
return;
}
// 生产环境检查
if (!origin) {
callback(null, true);
return;
}
// 检查是否在允许列表中
const isAllowed = allowedOrigins.some(allowedOrigin => {
if (allowedOrigin instanceof RegExp) {
return allowedOrigin.test(origin);
}
return allowedOrigin === origin;
});
if (isAllowed) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true // 允许携带凭证(cookies)
};
app.use(cors(corsOptions)); // 跨域检查
// - bodyParser
app.use(express.json()); // 解析json 请求
app.use(express.urlencoded({ extended: true })); // 解析url 编码
// - 指定静态文件目录 frontend/dist, frontend使用vue生成静态文件,单页面应用
// app.use(express.static(path.join(__dirname, '../frontend/dist')));
// 拆分路由组件
app.use('/api/v1', testRouter);
// 404处理
app.use('/api/*', (req, res) => {
res.status(404).json({
code: 404,
message: 'API endpoint not found',
data: null
});
});
// Start server (only in development)
if (process.env.NODE_ENV !== 'production') {
app.listen(PORT, () => {
console.log('\n' + '='.repeat(60));
console.log(` Backend server running on http://localhost:${PORT}`);
console.log(` API available at http://localhost:${PORT}/api/v1`);
console.log(' Hot reload enabled with nodemon');
console.log('='.repeat(60) + '\n');
});
}
// Export for Vercel
module.exports = app;创建路由
mkdir api/routers
touch api/routers/test.jstest.js
const express = require('express');
const router = express.Router();
router.get('/test', (req, res) => {
res.json({ message: 'Hello from the backend!' });
});
module.exports = router;设置.env文件
.env
PORT=3000
NODE_ENV=development创建nodemon.json文件
{
"watch": ["api/**/*.js"],
"ext": "js,json",
"ignore": ["node_modules/**", "frontend/**"],
"exec": "node api/index.js",
"env": {
"NODE_ENV": "development"
},
"restartable": "rs",
"colours": true,
"verbose": false,
"delay": 1000
}运行项目
npm run start访问http://localhost:3000/api/v1/test
{
"message": "Hello from the backend!"
}编辑vercel.json
{
"version": 2,
"builds": [
{
"src": "api/index.js",
"use": "@vercel/node"
},
{
"src": "frontend/package.json",
"use": "@vercel/static-build",
"config": {
"distDir": "dist"
}
}
],
"routes": [
{
"src": "/api/(.*)",
"dest": "/api/index.js"
},
{
"handle": "filesystem"
},
{
"src": "/(.*)",
"dest": "/frontend/$1"
}
]
}
添加supabase环境变量
.env
SUPABASE_URL=your-supabase-url
SUPABASE_ANON_KEY=your-supabase-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-supabase-service-role-key在backend中创建config文件
# pwd => backend
mkdir api/config
touch api/config/supabase_conifig.jssupabase_config.js
const { createClient } = require('@supabase/supabase-js');
require('dotenv').config();
// Supabase configuration
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY;
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!supabaseUrl || !supabaseAnonKey) {
console.warn('⚠️ Supabase credentials not found in environment variables');
console.warn('Please set SUPABASE_URL and SUPABASE_ANON_KEY in your .env file');
}
// Create Supabase client for public operations (with RLS)
const supabase = createClient(supabaseUrl, supabaseAnonKey);
// Create Supabase admin client (bypasses RLS - use with caution)
const supabaseAdmin = supabaseServiceKey
? createClient(supabaseUrl, supabaseServiceKey)
: null;
module.exports = {
supabase,
supabaseAdmin
};
创建一个测试文件test_supabase.js
# pwd=> backend
touch test_supabase.jstest_supabase.js
const { supabase, supabaseAdmin } = require('./api/config/supabase_conifig');
async function testSupabase() {
try {
// Test public read
const { data, error } = await supabase.from('test_table').select('*');
if (error) throw error;
console.log('Public read successful:', data);
// Test admin write
if (supabaseAdmin) {
const { data: insertData, error: insertError } = await supabaseAdmin
.from('test_table')
.insert([{ name: 'Test User', email: 'test@example.com' }]);
if (insertError) throw insertError;
console.log('Admin write successful:', insertData);
} else {
console.warn('⚠️ Service role key not found - admin write test skipped');
}
} catch (error) {
console.error('Supabase test failed:', error.message);
}
}
testSupabase();本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 WenYan Blog!
评论








