Skip to content

Commit 080a104

Browse files
committed
Use Prisma
1 parent 0bb5477 commit 080a104

File tree

13 files changed

+345
-214
lines changed

13 files changed

+345
-214
lines changed

README.md

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@
22
page_type: sample
33
languages:
44
- nodejs
5-
- javascript
6-
- tsql
5+
- typescript
76
- sql
8-
- json
97
products:
108
- azure
119
- vs-code
1210
- azure-sql-database
1311
- azure-functions
1412
- azure-web-apps
15-
description: "TodoMVC Sample app Full Stack implementation using Azure Static WebApps, Azure Functions, Node, Vue.Js and Azure SQL (full JSON support)"
13+
description: "TodoMVC Sample app Full Stack implementation using Prisma, Azure Static WebApps, Azure Functions, TypeScript, Nodejs, Vue.Js and Azure SQL (full JSON support)"
1614
urlFragment: "azure-sql-db-todo-mvc"
1715
---
1816

@@ -37,6 +35,8 @@ The implementation uses
3735
- [Azure Static WebApp](https://azure.microsoft.com/en-us/services/app-service/static/): to bind everything together in one easy package, natively integrated with GitHub CI/CD pipeline
3836
- [Vue.Js](https://vuejs.org/) as front-end client
3937
- [Azure Function](https://azure.microsoft.com/en-us/services/functions/) for providing serverless back-end infrastructure
38+
- [Prisma](https://www.prisma.io/) to interact with the Azure SQL database
39+
- [TypeScript](https://www.typescriptlang.org/) for the back-end logic
4040
- [NodeJS](https://nodejs.org/en/) for the back-end logic
4141
- [Azure SQL](https://azure.microsoft.com/en-us/services/sql-database/) as database to store ToDo data
4242
- [GitHub Actions](https://github.com/features/actions) to Deploy the full-stack website (thanks to Azure Static WebApps)
@@ -53,11 +53,26 @@ More details are available in this blog post: [TodoMVC Full Stack with Azure Sta
5353

5454
## Setup Database
5555

56-
Execute the `/database/create.sql` script on a database of your choice. Could be a local SQL Server or an Azure SQL running in the cloud. Just make sure the desired database is reachable by your local machine (eg: firewall, authentication and so on), then use SQL Server Management Studio or Azure Data Studio to run the script.
56+
Create a `.env` file by copying [.env.template](./api/.env.template) inside the [./api](./api) folder.
57+
58+
Define the database URL using the following format:
59+
```
60+
DATABASE_URL="sqlserver://DB_SERVER_NAME.database.windows.net:1433;database=DB_NAME;user=DB_USER@DB_SERVER_NAME;password={PASSWORD};encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30"
61+
```
62+
63+
The server could be a local SQL Server or an Azure SQL running in the cloud. Just make sure the desired database is reachable by your local machine (eg: firewall, authentication and so on).
64+
65+
To create the database schema, run the following command:
66+
```
67+
npx prisma migrate deploy
68+
```
69+
70+
> **Note:** `prisma migrate deploy` will run the migration in the repository. To automatically create SQL migrations based on changes in your Prisma schema and run them use the `prisma migrate dev` command. If you're using Azure SQL and the `prisma migrate dev` command, you will need to also set the URL of the [shadow database](https://www.prisma.io/docs/concepts/components/prisma-migrate/shadow-database#cloud-hosted-shadow-databases-must-be-created-manually) for development purposes.
71+
5772

5873
Of course if you want to deploy the solution on Azure, use Azure SQL.
5974

60-
If you need any help in executing the SQL script on Azure SQL, you can find a Quickstart here: [Use Azure Data Studio to connect and query Azure SQL database](https://docs.microsoft.com/en-us/sql/azure-data-studio/quickstart-sql-database).
75+
To learn more about [Prisma Migrate](https://www.prisma.io/migrate), check out the [Prisma Migrate docs](https://www.prisma.io/docs/concepts/components/prisma-migrate)
6176

6277
If you need to create an Azure SQL database from scratch, an Azure SQL S0 database would be more than fine to run the tests.
6378

api/.env.template

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
db_server=".database.windows.net"
2-
db_user="webapp"
3-
db_password="Super_Str0ng*P@ZZword!"
4-
db_database=""
1+
DATABASE_URL="sqlserver://DB_SERVER_NAME.database.windows.net:1433;database=DB_NAME;user=DB_USER@DB_SERVER_NAME;password={PASSWORD};encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30"

api/.gitignore

Lines changed: 3 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,4 @@
1-
# Logs
2-
logs
3-
*.log
4-
npm-debug.log*
5-
yarn-debug.log*
6-
yarn-error.log*
7-
lerna-debug.log*
8-
9-
# Diagnostic reports (https://nodejs.org/api/report.html)
10-
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11-
12-
# Runtime data
13-
pids
14-
*.pid
15-
*.seed
16-
*.pid.lock
17-
18-
# Directory for instrumented libs generated by jscoverage/JSCover
19-
lib-cov
20-
21-
# Coverage directory used by tools like istanbul
22-
coverage
23-
24-
# nyc test coverage
25-
.nyc_output
26-
27-
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
28-
.grunt
29-
30-
# Bower dependency directory (https://bower.io/)
31-
bower_components
32-
33-
# node-waf configuration
34-
.lock-wscript
35-
36-
# Compiled binary addons (https://nodejs.org/api/addons.html)
37-
build/Release
38-
39-
# Dependency directories
40-
node_modules/
41-
jspm_packages/
42-
43-
# TypeScript v1 declaration files
44-
typings/
45-
46-
# Optional npm cache directory
47-
.npm
48-
49-
# Optional eslint cache
50-
.eslintcache
51-
52-
# Optional REPL history
53-
.node_repl_history
54-
55-
# Output of 'npm pack'
56-
*.tgz
57-
58-
# Yarn Integrity file
59-
.yarn-integrity
60-
61-
# dotenv environment variables file
1+
node_modules
2+
# Keep environment variables out of version control
623
.env
63-
.env.test
64-
65-
# parcel-bundler cache (https://parceljs.org/)
66-
.cache
67-
68-
# next.js build output
69-
.next
70-
71-
# nuxt.js build output
72-
.nuxt
73-
74-
# vuepress build output
75-
.vuepress/dist
76-
77-
# Serverless directories
78-
.serverless/
79-
80-
# FuseBox cache
81-
.fusebox/
82-
83-
# DynamoDB Local files
84-
.dynamodb/
85-
86-
# TypeScript output
87-
dist
88-
out
89-
90-
# Azure Functions artifacts
91-
bin
92-
obj
93-
appsettings.json
94-
local.settings.json
95-
.env
4+
dist/

api/todo/function.json renamed to api/TodoFunction/function.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@
55
"type": "httpTrigger",
66
"direction": "in",
77
"name": "req",
8-
"methods": [
9-
"get", "put", "post", "delete"
10-
],
8+
"methods": ["get", "put", "post", "delete"],
119
"route": "todo/{id:int?}"
1210
},
1311
{
1412
"type": "http",
1513
"direction": "out",
1614
"name": "res"
1715
}
18-
]
16+
],
17+
"scriptFile": "../dist/TodoFunction/index.js"
1918
}

api/TodoFunction/index.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { AzureFunction, Context, HttpRequest } from '@azure/functions'
2+
import { PrismaClient, Todo } from '@prisma/client'
3+
4+
const prisma = new PrismaClient()
5+
6+
const httpTrigger: AzureFunction = async function (
7+
context: Context,
8+
req: HttpRequest,
9+
): Promise<any> {
10+
const method = req.method.toLowerCase()
11+
12+
switch (method) {
13+
case 'get':
14+
await getTodo(context, req?.params?.id)
15+
break
16+
case 'post':
17+
await createTodo(context, req.body)
18+
break
19+
case 'put':
20+
await updateTodo(context, req?.params?.id, req.body)
21+
break
22+
case 'delete':
23+
await deleteTodo(context, req?.params?.id)
24+
break
25+
}
26+
}
27+
28+
export default httpTrigger
29+
30+
async function getTodo(context: Context, id: string): Promise<void> {
31+
const parsedId = parseInt(id, 10)
32+
if (isNaN(parsedId)) {
33+
context.res.status = 400
34+
return
35+
}
36+
37+
const todo = await prisma.todo.findUnique({
38+
where: {
39+
id: parsedId,
40+
},
41+
})
42+
43+
if (todo) {
44+
context.res.body = todo
45+
} else {
46+
context.res = { status: 404 }
47+
}
48+
}
49+
50+
async function createTodo(context: Context, body: unknown) {
51+
if (!isTodo(body)) {
52+
context.res.status = 400
53+
return
54+
}
55+
56+
try {
57+
const todo = await prisma.todo.create({
58+
data: {
59+
todo: body.todo,
60+
completed: body.completed === 'true' || undefined,
61+
},
62+
})
63+
context.res.body = todo
64+
} catch (e) {
65+
context.res.body = e
66+
context.res.status = 500
67+
}
68+
}
69+
70+
async function deleteTodo(context: Context, id: string) {
71+
const parsedId = parseInt(id, 10)
72+
if (isNaN(parsedId)) {
73+
context.res.status = 400
74+
return
75+
}
76+
77+
try {
78+
const todo = await prisma.todo.delete({
79+
where: {
80+
id: parsedId,
81+
},
82+
})
83+
context.res.body = todo
84+
} catch (e) {
85+
context.res.status = 500
86+
}
87+
}
88+
89+
async function updateTodo(context: Context, id: string, body: unknown) {
90+
if (!isTodo(body) || !id) {
91+
context.res.status = 400
92+
return
93+
}
94+
95+
try {
96+
// convert to boolean and undefined if not passed in so it doesn't change in the DB
97+
const completed =
98+
body.completed === 'true'
99+
? true
100+
: body.completed === 'false'
101+
? false
102+
: undefined
103+
104+
const todo = await prisma.todo.update({
105+
data: {
106+
todo: body.todo,
107+
completed,
108+
},
109+
where: {
110+
id: parseInt(id, 10),
111+
},
112+
})
113+
context.res.body = todo
114+
} catch (e) {
115+
context.log(e)
116+
context.res.status = 500
117+
}
118+
}
119+
120+
// Type definition for the request input of a todo
121+
interface InputTodo {
122+
id?: string
123+
completed?: string
124+
todo: string
125+
}
126+
127+
// Function to validate at runtime while giving type safety
128+
const isTodo = (todo: unknown): todo is InputTodo => {
129+
return typeof todo === 'object' && ('todo' in todo || 'completed' in todo)
130+
}

api/local.settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"IsEncrypted": false,
3+
"Values": {
4+
"FUNCTIONS_WORKER_RUNTIME": "node",
5+
"AzureWebJobsStorage": ""
6+
}
7+
}

0 commit comments

Comments
 (0)