|
1 | | -# REST API Example |
| 1 | +# ZenStack Express.js Tutorial Project |
2 | 2 |
|
3 | | -This example shows how to implement a **REST API with TypeScript** using [Express](https://expressjs.com/) and [Prisma Client](https://www.prisma.io/docs/concepts/components/prisma-client). The example uses an SQLite database file with some initial dummy data which you can find at [`./prisma/dev.db`](./prisma/dev.db). |
| 3 | +This is a sample project for demonstrating how to use ZenStack in backend development. |
4 | 4 |
|
5 | | -## Getting started |
6 | | - |
7 | | -### 1. Download example and install dependencies |
8 | | - |
9 | | -Download this example: |
10 | | - |
11 | | -``` |
12 | | -npx try-prisma --template typescript/rest-express |
13 | | -``` |
14 | | - |
15 | | -Install npm dependencies: |
16 | | - |
17 | | -``` |
18 | | -cd rest-express |
19 | | -npm install |
20 | | -``` |
21 | | - |
22 | | -<details><summary><strong>Alternative:</strong> Clone the entire repo</summary> |
23 | | - |
24 | | -Clone this repository: |
25 | | - |
26 | | -``` |
27 | | -git clone git@github.com:prisma/prisma-examples.git --depth=1 |
28 | | -``` |
29 | | - |
30 | | -Install npm dependencies: |
31 | | - |
32 | | -``` |
33 | | -cd prisma-examples/typescript/rest-express |
34 | | -npm install |
35 | | -``` |
36 | | - |
37 | | -</details> |
38 | | - |
39 | | -### 2. Create and seed the database |
40 | | - |
41 | | -Run the following command to create your SQLite database file. This also creates the `User` and `Post` tables that are defined in [`prisma/schema.prisma`](./prisma/schema.prisma): |
42 | | - |
43 | | -``` |
44 | | -npx prisma migrate dev --name init |
45 | | -``` |
46 | | - |
47 | | -When `npx prisma migrate dev` is executed against a newly created database, seeding is also triggered. The seed file in [`prisma/seed.ts`](./prisma/seed.ts) will be executed and your database will be populated with the sample data. |
48 | | - |
49 | | - |
50 | | -### 3. Start the REST API server |
51 | | - |
52 | | -``` |
53 | | -npm run dev |
54 | | -``` |
55 | | - |
56 | | -The server is now running on `http://localhost:3000`. You can now run the API requests, e.g. [`http://localhost:3000/feed`](http://localhost:3000/feed). |
57 | | - |
58 | | -## Using the REST API |
59 | | - |
60 | | -You can access the REST API of the server using the following endpoints: |
61 | | - |
62 | | -### `GET` |
63 | | - |
64 | | -- `/post/:id`: Fetch a single post by its `id` |
65 | | -- `/feed?searchString={searchString}&take={take}&skip={skip}&orderBy={orderBy}`: Fetch all _published_ posts |
66 | | - - Query Parameters |
67 | | - - `searchString` (optional): This filters posts by `title` or `content` |
68 | | - - `take` (optional): This specifies how many objects should be returned in the list |
69 | | - - `skip` (optional): This specifies how many of the returned objects in the list should be skipped |
70 | | - - `orderBy` (optional): The sort order for posts in either ascending or descending order. The value can either `asc` or `desc` |
71 | | -- `/user/:id/drafts`: Fetch user's drafts by their `id` |
72 | | -- `/users`: Fetch all users |
73 | | -### `POST` |
74 | | - |
75 | | -- `/post`: Create a new post |
76 | | - - Body: |
77 | | - - `title: String` (required): The title of the post |
78 | | - - `content: String` (optional): The content of the post |
79 | | - - `authorEmail: String` (required): The email of the user that creates the post |
80 | | -- `/signup`: Create a new user |
81 | | - - Body: |
82 | | - - `email: String` (required): The email address of the user |
83 | | - - `name: String` (optional): The name of the user |
84 | | - - `postData: PostCreateInput[]` (optional): The posts of the user |
85 | | - |
86 | | -### `PUT` |
87 | | - |
88 | | -- `/publish/:id`: Toggle the publish value of a post by its `id` |
89 | | -- `/post/:id/views`: Increases the `viewCount` of a `Post` by one `id` |
90 | | - |
91 | | -### `DELETE` |
92 | | - |
93 | | -- `/post/:id`: Delete a post by its `id` |
94 | | - |
95 | | - |
96 | | -## Evolving the app |
97 | | - |
98 | | -Evolving the application typically requires two steps: |
99 | | - |
100 | | -1. Migrate your database using Prisma Migrate |
101 | | -1. Update your application code |
102 | | - |
103 | | -For the following example scenario, assume you want to add a "profile" feature to the app where users can create a profile and write a short bio about themselves. |
104 | | - |
105 | | -### 1. Migrate your database using Prisma Migrate |
106 | | - |
107 | | -The first step is to add a new table, e.g. called `Profile`, to the database. You can do this by adding a new model to your [Prisma schema file](./prisma/schema.prisma) file and then running a migration afterwards: |
108 | | - |
109 | | -```diff |
110 | | -// ./prisma/schema.prisma |
111 | | - |
112 | | -model User { |
113 | | - id Int @default(autoincrement()) @id |
114 | | - name String? |
115 | | - email String @unique |
116 | | - posts Post[] |
117 | | -+ profile Profile? |
118 | | -} |
119 | | - |
120 | | -model Post { |
121 | | - id Int @id @default(autoincrement()) |
122 | | - createdAt DateTime @default(now()) |
123 | | - updatedAt DateTime @updatedAt |
124 | | - title String |
125 | | - content String? |
126 | | - published Boolean @default(false) |
127 | | - viewCount Int @default(0) |
128 | | - author User? @relation(fields: [authorId], references: [id]) |
129 | | - authorId Int? |
130 | | -} |
131 | | - |
132 | | -+model Profile { |
133 | | -+ id Int @default(autoincrement()) @id |
134 | | -+ bio String? |
135 | | -+ user User @relation(fields: [userId], references: [id]) |
136 | | -+ userId Int @unique |
137 | | -+} |
138 | | -``` |
139 | | - |
140 | | -Once you've updated your data model, you can execute the changes against your database with the following command: |
141 | | - |
142 | | -``` |
143 | | -npx prisma migrate dev --name add-profile |
144 | | -``` |
145 | | - |
146 | | -This adds another migration to the `prisma/migrations` directory and creates the new `Profile` table in the database. |
147 | | - |
148 | | -### 2. Update your application code |
149 | | - |
150 | | -You can now use your `PrismaClient` instance to perform operations against the new `Profile` table. Those operations can be used to implement API endpoints in the REST API. |
151 | | - |
152 | | -#### 2.1 Add the API endpoint to your app |
153 | | - |
154 | | -Update your `index.ts` file by adding a new endpoint to your API: |
155 | | - |
156 | | -```ts |
157 | | -app.post('/user/:id/profile', async (req, res) => { |
158 | | - const { id } = req.params |
159 | | - const { bio } = req.body |
160 | | - |
161 | | - const profile = await prisma.profile.create({ |
162 | | - data: { |
163 | | - bio, |
164 | | - user: { |
165 | | - connect: { |
166 | | - id: Number(id) |
167 | | - } |
168 | | - } |
169 | | - } |
170 | | - }) |
171 | | - |
172 | | - res.json(profile) |
173 | | -}) |
174 | | -``` |
175 | | - |
176 | | -#### 2.2 Testing out your new endpoint |
177 | | - |
178 | | -Restart your application server and test out your new endpoint. |
179 | | - |
180 | | -##### `POST` |
181 | | - |
182 | | -- `/user/:id/profile`: Create a new profile based on the user id |
183 | | - - Body: |
184 | | - - `bio: String` : The bio of the user |
185 | | - |
186 | | - |
187 | | -<details><summary>Expand to view more sample Prisma Client queries on <code>Profile</code></summary> |
188 | | - |
189 | | -Here are some more sample Prisma Client queries on the new <code>Profile</code> model: |
190 | | - |
191 | | -##### Create a new profile for an existing user |
192 | | - |
193 | | -```ts |
194 | | -const profile = await prisma.profile.create({ |
195 | | - data: { |
196 | | - bio: 'Hello World', |
197 | | - user: { |
198 | | - connect: { email: 'alice@prisma.io' }, |
199 | | - }, |
200 | | - }, |
201 | | -}) |
202 | | -``` |
203 | | - |
204 | | -##### Create a new user with a new profile |
205 | | - |
206 | | -```ts |
207 | | -const user = await prisma.user.create({ |
208 | | - data: { |
209 | | - email: 'john@prisma.io', |
210 | | - name: 'John', |
211 | | - profile: { |
212 | | - create: { |
213 | | - bio: 'Hello World', |
214 | | - }, |
215 | | - }, |
216 | | - }, |
217 | | -}) |
218 | | -``` |
219 | | - |
220 | | -##### Update the profile of an existing user |
221 | | - |
222 | | -```ts |
223 | | -const userWithUpdatedProfile = await prisma.user.update({ |
224 | | - where: { email: 'alice@prisma.io' }, |
225 | | - data: { |
226 | | - profile: { |
227 | | - update: { |
228 | | - bio: 'Hello Friends', |
229 | | - }, |
230 | | - }, |
231 | | - }, |
232 | | -}) |
233 | | -``` |
234 | | - |
235 | | -</details> |
236 | | - |
237 | | -## Switch to another database (e.g. PostgreSQL, MySQL, SQL Server, MongoDB) |
238 | | - |
239 | | -If you want to try this example with another database than SQLite, you can adjust the the database connection in [`prisma/schema.prisma`](./prisma/schema.prisma) by reconfiguring the `datasource` block. |
240 | | - |
241 | | -Learn more about the different connection configurations in the [docs](https://www.prisma.io/docs/reference/database-reference/connection-urls). |
242 | | - |
243 | | -<details><summary>Expand for an overview of example configurations with different databases</summary> |
244 | | - |
245 | | -### PostgreSQL |
246 | | - |
247 | | -For PostgreSQL, the connection URL has the following structure: |
248 | | - |
249 | | -```prisma |
250 | | -datasource db { |
251 | | - provider = "postgresql" |
252 | | - url = "postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA" |
253 | | -} |
254 | | -``` |
255 | | - |
256 | | -Here is an example connection string with a local PostgreSQL database: |
257 | | - |
258 | | -```prisma |
259 | | -datasource db { |
260 | | - provider = "postgresql" |
261 | | - url = "postgresql://janedoe:mypassword@localhost:5432/notesapi?schema=public" |
262 | | -} |
263 | | -``` |
264 | | - |
265 | | -### MySQL |
266 | | - |
267 | | -For MySQL, the connection URL has the following structure: |
268 | | - |
269 | | -```prisma |
270 | | -datasource db { |
271 | | - provider = "mysql" |
272 | | - url = "mysql://USER:PASSWORD@HOST:PORT/DATABASE" |
273 | | -} |
274 | | -``` |
275 | | - |
276 | | -Here is an example connection string with a local MySQL database: |
277 | | - |
278 | | -```prisma |
279 | | -datasource db { |
280 | | - provider = "mysql" |
281 | | - url = "mysql://janedoe:mypassword@localhost:3306/notesapi" |
282 | | -} |
283 | | -``` |
284 | | - |
285 | | -### Microsoft SQL Server |
286 | | - |
287 | | -Here is an example connection string with a local Microsoft SQL Server database: |
288 | | - |
289 | | -```prisma |
290 | | -datasource db { |
291 | | - provider = "sqlserver" |
292 | | - url = "sqlserver://localhost:1433;initial catalog=sample;user=sa;password=mypassword;" |
293 | | -} |
294 | | -``` |
295 | | - |
296 | | -### MongoDB |
297 | | - |
298 | | -Here is an example connection string with a local MongoDB database: |
299 | | - |
300 | | -```prisma |
301 | | -datasource db { |
302 | | - provider = "mongodb" |
303 | | - url = "mongodb://USERNAME:PASSWORD@HOST/DATABASE?authSource=admin&retryWrites=true&w=majority" |
304 | | -} |
305 | | -``` |
306 | | -Because MongoDB is currently in [Preview](https://www.prisma.io/docs/about/releases#preview), you need to specify the `previewFeatures` on your `generator` block: |
307 | | - |
308 | | -``` |
309 | | -generator client { |
310 | | - provider = "prisma-client-js" |
311 | | - previewFeatures = ["mongodb"] |
312 | | -} |
313 | | -``` |
314 | | -</details> |
315 | | - |
316 | | -## Next steps |
317 | | - |
318 | | -- Check out the [Prisma docs](https://www.prisma.io/docs) |
319 | | -- Share your feedback in the [`prisma2`](https://prisma.slack.com/messages/CKQTGR6T0/) channel on the [Prisma Slack](https://slack.prisma.io/) |
320 | | -- Create issues and ask questions on [GitHub](https://github.com/prisma/prisma/) |
321 | | -- Watch our biweekly "What's new in Prisma" livestreams on [Youtube](https://www.youtube.com/channel/UCptAHlN1gdwD89tFM3ENb6w) |
| 5 | +See documentation [here](https://zenstack.dev/docs/get-started/backend). |
0 commit comments