|
| 1 | +# Your tasks |
| 2 | + |
| 3 | +IMPORTANT: For all the tasks, focus on best practices. Aim for solutions that not only work but that you'd be happy to push to a production app. |
| 4 | + |
| 5 | +## 1. Draft post bugfix |
| 6 | + |
| 7 | +After running this app, you'll notice that the backend crashes if you try adding a new draft post with an email address that doesn't belong to any existing user. To reproduce this issue: |
| 8 | + |
| 9 | +- Click "Create draft" |
| 10 | +- Enter a title |
| 11 | +- Enter an email address that doesn't belong to any user, for example ' [email protected]' |
| 12 | +- Enter content |
| 13 | +- Click "Create" |
| 14 | +- You'll see the server crash with the following error message: |
| 15 | + |
| 16 | +``` |
| 17 | +/Users/polygon/Documents/development.nosync/recruitment-task/backend/node_modules/@prisma/client/runtime/index.js:45405 |
| 18 | + throw new PrismaClientKnownRequestError(message, e.code, this.client._clientVersion, e.meta); |
| 19 | + ^ |
| 20 | +PrismaClientKnownRequestError: |
| 21 | +Invalid `prisma.post.create()` invocation in |
| 22 | +/Users/polygon/Documents/development.nosync/recruitment-task/backend/src/index.ts:50:36 |
| 23 | +
|
| 24 | + 47 |
| 25 | + 48 app.post(`/post`, async (req, res) => { |
| 26 | + 49 const { title, content, authorEmail } = req.body |
| 27 | +→ 50 const result = await prisma.post.create( |
| 28 | + An operation failed because it depends on one or more records that were required but not found. No 'User' record(s) (needed to inline the relation on 'Post' record(s)) was found for a nested connect on one-to-many relation 'PostToUser'. |
| 29 | + at Object.request (/Users/polygon/Documents/development.nosync/recruitment-task/backend/node_modules/@prisma/client/runtime/index.js:45405:15) |
| 30 | + at async PrismaClient._request (/Users/polygon/Documents/development.nosync/recruitment-task/backend/node_modules/@prisma/client/runtime/index.js:46301:18) { |
| 31 | + code: 'P2025', |
| 32 | + clientVersion: '3.14.0', |
| 33 | + meta: { |
| 34 | + cause: "No 'User' record(s) (needed to inline the relation on 'Post' record(s)) was found for a nested connect on one-to-many relation 'PostToUser'." |
| 35 | + } |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +There are many ways to address this bug, please address it using a solution of your choice. Remember that a good solution is not only technically sound but should also result in good user experience. |
| 40 | + |
| 41 | +## 2. Leaving comments |
| 42 | + |
| 43 | +Make it possible for users to leave comments under published posts. |
| 44 | + |
| 45 | +## 3. Typing indicator |
| 46 | + |
| 47 | +You're probaly familiar with typing indicators such as [this one](https://support.signal.org/hc/en-us/articles/360020798451-Typing-Indicators). |
| 48 | + |
| 49 | +Now that users can add comments, add a typing indicator to show others that someone is writing a comment. |
| 50 | + |
| 51 | +## 4. Be prepared to discuss your solutions with us :) |
| 52 | + |
| 53 | +During the technical interview, among other things, we're going to discuss how you approached the tasks. We're probably going to ask you about why you decided on a specific solution, pros and cons of different ways of addressing the same problem etc. |
| 54 | + |
| 55 | +<br /> |
| 56 | + |
| 57 | +# ⬇️ PROJECT DOCUMENTATION ⬇️ |
| 58 | + |
| 59 | +# Fullstack Example with Next.js (REST API) |
| 60 | + |
| 61 | +This example shows how to implement a **fullstack app in TypeScript with [Next.js](https://nextjs.org/)** using [React](https://reactjs.org/) (frontend), [Express](https://expressjs.com/) and [Prisma Client](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client) (backend). It uses a SQLite database file with some initial dummy data which you can find at [`./backend/prisma/dev.db`](./backend/prisma/dev.db). |
| 62 | + |
| 63 | +## Getting Started |
| 64 | + |
| 65 | +### 1. Install npm dependencies: |
| 66 | + |
| 67 | +Install dependencies for your [`backend`](./backend). Open a terminal window and install the `backend`'s dependencies |
| 68 | + |
| 69 | +```bash |
| 70 | +cd backend |
| 71 | +npm install |
| 72 | +``` |
| 73 | + |
| 74 | +Open a separate terminal window and navigate to your [`frontend`](./frontend) directory and install its dependencies |
| 75 | + |
| 76 | +```bash |
| 77 | +cd frontend |
| 78 | +npm install |
| 79 | +``` |
| 80 | + |
| 81 | +### 2. Create and seed the database (backend) |
| 82 | + |
| 83 | +On the terminal window used to install the backend npm dependencies, 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`](./backend/prisma/schema.prisma): |
| 84 | + |
| 85 | +``` |
| 86 | +npx prisma migrate dev --name init |
| 87 | +``` |
| 88 | + |
| 89 | +When `npx prisma migrate dev` is executed against a newly created database, seeding is also triggered. The seed file in [`prisma/seed.ts`](./backend/prisma/seed.ts) will be executed and your database will be populated with the sample data. |
| 90 | + |
| 91 | +### 3. Start the server (backend) |
| 92 | + |
| 93 | +On the same terminal used in step 2, run the following command to start the server: |
| 94 | + |
| 95 | +```bash |
| 96 | +npm run dev |
| 97 | +``` |
| 98 | + |
| 99 | +The server is now running at [`http://localhost:3001/`](http://localhost:3001/). |
| 100 | + |
| 101 | +### 4. Start the app (frontend) |
| 102 | + |
| 103 | +On the terminal window used to install frontend npm dependencies, run the following command to start the app: |
| 104 | + |
| 105 | +```bash |
| 106 | +npm run dev |
| 107 | +``` |
| 108 | + |
| 109 | +The app is now running, navigate to [`http://localhost:3000/`](http://localhost:3000/) in your browser to explore its UI. |
| 110 | + |
| 111 | +<details><summary>Expand for a tour through the UI of the app</summary> |
| 112 | + |
| 113 | +<br /> |
| 114 | + |
| 115 | +**Blog** (located in [`./pages/index.tsx`](./pages/index.tsx)) |
| 116 | + |
| 117 | + |
| 118 | + |
| 119 | +**Signup** (located in [`./pages/signup.tsx`](./pages/signup.tsx)) |
| 120 | + |
| 121 | + |
| 122 | + |
| 123 | +**Create post (draft)** (located in [`./pages/create.tsx`](./pages/create.tsx)) |
| 124 | + |
| 125 | + |
| 126 | + |
| 127 | +**Drafts** (located in [`./pages/drafts.tsx`](./pages/drafts.tsx)) |
| 128 | + |
| 129 | + |
| 130 | + |
| 131 | +**View post** (located in [`./pages/p/[id].tsx`](./pages/p/[id].tsx)) (delete or publish here) |
| 132 | + |
| 133 | + |
| 134 | + |
| 135 | +</details> |
| 136 | + |
| 137 | +## Using the REST API |
| 138 | + |
| 139 | +You can also access the REST API of the API server directly. It is running [`localhost:3001`](http://localhost:3001) (so you can e.g. reach the API with [`localhost:3000/feed`](http://localhost:3001/feed)). |
| 140 | + |
| 141 | +### `GET` |
| 142 | + |
| 143 | +- `/api/post/:id`: Fetch a single post by its `id` |
| 144 | +- `/api/feed`: Fetch all _published_ posts |
| 145 | +- `/api/filterPosts?searchString={searchString}`: Filter posts by `title` or `content` |
| 146 | + |
| 147 | +### `POST` |
| 148 | + |
| 149 | +- `/api/post`: Create a new post |
| 150 | + - Body: |
| 151 | + - `title: String` (required): The title of the post |
| 152 | + - `content: String` (optional): The content of the post |
| 153 | + - `authorEmail: String` (required): The email of the user that creates the post |
| 154 | +- `/api/user`: Create a new user |
| 155 | + - Body: |
| 156 | + - `email: String` (required): The email address of the user |
| 157 | + - `name: String` (optional): The name of the user |
| 158 | + |
| 159 | +### `PUT` |
| 160 | + |
| 161 | +- `/api/publish/:id`: Publish a post by its `id` |
| 162 | + |
| 163 | +### `DELETE` |
| 164 | + |
| 165 | +- `/api/post/:id`: Delete a post by its `id` |
| 166 | + |
| 167 | +## Evolving the app |
| 168 | + |
| 169 | +Evolving the application typically requires three steps: |
| 170 | + |
| 171 | +1. Migrate your database using Prisma Migrate |
| 172 | +1. Update your server-side application code |
| 173 | +1. Build new UI features in React |
| 174 | + |
| 175 | +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. |
| 176 | + |
| 177 | +### 1. Migrate your database using Prisma Migrate |
| 178 | + |
| 179 | +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: |
| 180 | + |
| 181 | +```diff |
| 182 | +// schema.prisma |
| 183 | + |
| 184 | +model Post { |
| 185 | + id Int @default(autoincrement()) @id |
| 186 | + title String |
| 187 | + content String? |
| 188 | + published Boolean @default(false) |
| 189 | + author User? @relation(fields: [authorId], references: [id]) |
| 190 | + authorId Int |
| 191 | +} |
| 192 | + |
| 193 | +model User { |
| 194 | + id Int @default(autoincrement()) @id |
| 195 | + name String? |
| 196 | + email String @unique |
| 197 | + posts Post[] |
| 198 | ++ profile Profile? |
| 199 | +} |
| 200 | + |
| 201 | ++model Profile { |
| 202 | ++ id Int @default(autoincrement()) @id |
| 203 | ++ bio String? |
| 204 | ++ userId Int @unique |
| 205 | ++ user User @relation(fields: [userId], references: [id]) |
| 206 | ++} |
| 207 | +``` |
| 208 | + |
| 209 | +Once you've updated your data model, you can execute the changes against your database with the following command: |
| 210 | + |
| 211 | +``` |
| 212 | +npx prisma migrate dev |
| 213 | +``` |
| 214 | + |
| 215 | +### 2. Update your application code |
| 216 | + |
| 217 | +You can now use your `PrismaClient` instance to perform operations against the new `Profile` table. Here are some examples: |
| 218 | + |
| 219 | +#### Create a new profile for an existing user |
| 220 | + |
| 221 | +```ts |
| 222 | +const profile = await prisma.profile.create({ |
| 223 | + data: { |
| 224 | + bio: "Hello World", |
| 225 | + user: { |
| 226 | + connect: { email: "[email protected]" }, |
| 227 | + }, |
| 228 | + }, |
| 229 | +}); |
| 230 | +``` |
| 231 | + |
| 232 | +#### Create a new user with a new profile |
| 233 | + |
| 234 | +```ts |
| 235 | +const user = await prisma.user.create({ |
| 236 | + data: { |
| 237 | + |
| 238 | + name: "John", |
| 239 | + profile: { |
| 240 | + create: { |
| 241 | + bio: "Hello World", |
| 242 | + }, |
| 243 | + }, |
| 244 | + }, |
| 245 | +}); |
| 246 | +``` |
| 247 | + |
| 248 | +#### Update the profile of an existing user |
| 249 | + |
| 250 | +```ts |
| 251 | +const userWithUpdatedProfile = await prisma.user.update({ |
| 252 | + where: { email: "[email protected]" }, |
| 253 | + data: { |
| 254 | + profile: { |
| 255 | + update: { |
| 256 | + bio: "Hello Friends", |
| 257 | + }, |
| 258 | + }, |
| 259 | + }, |
| 260 | +}); |
| 261 | +``` |
| 262 | + |
| 263 | +### 3. Build new UI features in React |
| 264 | + |
| 265 | +Once you have added a new endpoint to the API (e.g. `/api/profile` with `/POST`, `/PUT` and `GET` operations), you can start building a new UI component in React. It could e.g. be called `profile.tsx` and would be located in the `pages` directory. |
| 266 | + |
| 267 | +In the application code, you can access the new endpoint via `fetch` operations and populate the UI with the data you receive from the API calls. |
0 commit comments