Skip to content

Commit

Permalink
demonstrate extracting an endpoint type from server
Browse files Browse the repository at this point in the history
in this commit I demonstrate to @surmavagit how the return data type of
an endpoint can be extracted to the moon tool project called queries
which is located at `packages/prisma-queries` in the repo

in this example, the endpoint /api/tables/mine uses a combination of a
`findMany` Prisma query (on the TablePhysical model) and a mapping function
to produce the results of the endpoint

the example extracts both the query parameters and the mapper function
and uses some Prisma and Typescript magic to produce the exact type to
be returned by the endpoint automatically

this way, the frontend will be able to import the same type using:
```
import { TableWithOrders } from "prisma-queries"
```
  • Loading branch information
diraneyya committed May 25, 2023
1 parent 1fba3ac commit 6a953fc
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 56 deletions.
3 changes: 2 additions & 1 deletion apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.1",
"sass": "^1.62.1"
"sass": "^1.62.1",
"prisma-queries": "workspace:*"
},
"devDependencies": {
"@types/prop-types": "^15.7.5",
Expand Down
34 changes: 1 addition & 33 deletions apps/client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,4 @@ export type {
UserType,
} from "@prisma/client";

/*
I used this article to create the required types from the prisma client itself.
https://www.prisma.io/docs/concepts/components/prisma-client/advanced-type-safety/operating-against-partial-structures-of-model-types#problem-using-variations-of-the-generated-model-type
*/

import { Prisma } from "@prisma/client";

const tableWithOrders = Prisma.validator<Prisma.OrderArgs>()({
select: {
id: true,
menuItem: {
select: {
name: true,
price: true,
},
},
orderTime: true,
statusId: true,
status: {
select: {
name: true,
},
},
waiterId: true,
waiter: {
select: {
name: true,
},
},
},
});

export type TableWithOrders = Prisma.OrderGetPayload<typeof tableWithOrders>
export type { TableWithOrders } from "prisma-queries";
26 changes: 7 additions & 19 deletions apps/server/src/handlers/tables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { UserInfo } from "os";
import { prisma } from "..";
import { NextFunction, Request, Response } from "express";
import { RequestTableId, RequestUserInfo } from "../types";
import { queryMyTablesWithOrders } from "prisma-queries";
import { queryMyTablesWithOrders, mapMyTablesWithOrders } from "prisma-queries";

export const getAllTables = async (_: Request, res: Response) => {
prisma.tablePhysical
Expand All @@ -17,26 +17,14 @@ export const getAllTables = async (_: Request, res: Response) => {
};

export const getMyTablesWithOrders = async (req: Request & RequestUserInfo, res: Response) => {
const prismaQuery = queryMyTablesWithOrders(req.userInfo.id);
const prismaQuery = {
...queryMyTablesWithOrders(req.userInfo.id)
};
const resultMapping = mapMyTablesWithOrders;
prisma.tablePhysical
.findMany(prismaQuery)
.then((found) => {
const result = found.map((table) => ({
id: table.id,
name: table.name,
statusId: table.statusId,
status: table.tableStatus.name,
orders: table.orders.map((order) => ({
id: order.id,
name: order.menuItem.name,
price: order.menuItem.price,
orderTime: order.orderTime,
statusId: order.statusId,
status: order.status.name,
waiterId: order.waiterId,
waiter: order.waiter.name,
})),
}));
.then(resultMapping)
.then((result) => {
res.json(result);
})
.catch((err) => {
Expand Down
7 changes: 7 additions & 0 deletions packages/prisma-queries/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ fileGroups:
tasks:
build:
command: "tsc"
inputs:
- "@group(indexFile)"
outputs:
- "@group(exportFiles)"
dev:
command: "tsc --watch"
local: true
inputs:
- "@group(indexFile)"
outputs:
Expand Down
53 changes: 53 additions & 0 deletions packages/prisma-queries/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
/*
I used this article to create the required types from the prisma client itself.
https://www.prisma.io/docs/concepts/components/prisma-client/advanced-type-safety/operating-against-partial-structures-of-model-types#problem-using-variations-of-the-generated-model-type
Note that since @surmavagit decided not only to use Prisma.**.findMany in some cases, but
also to apply additional mapping functions, the process of extracting the types had become
slightly more involved. Namely:
(1) Extract the query parameter object from server project (this is passed to "findMany")
(2) Use the article above to compute the type to be returned from the query
(3) If a mapping function is used to map the results, extract the mapping function
(4) To compute the type returned by the endpoint, use TS utility function ReturnType
*/

import { Prisma } from "@prisma/client";

/*
* ------------------------------------------
* Type extraction of the endpoint: (EXAMPLE)
*
* GET /api/tables/mine
* ------------------------------------------
*/

// (1) Extract and export the query parameter object (as a function in this case, since
// it depends on user-supplied waiter ID)
export const queryMyTablesWithOrders = (id: number) => ({
where: {
orders: {
Expand Down Expand Up @@ -41,3 +67,30 @@ export const queryMyTablesWithOrders = (id: number) => ({
},
},
});

// (2) Compute the type to be returned by the Prisma query (using findMany on the
// TablePhysical model in this case)
const tablesWithOrders = Prisma.validator<Prisma.TablePhysicalFindManyArgs>()(queryMyTablesWithOrders(0));
type TableWithOrdersQuery = Prisma.TablePhysicalGetPayload<typeof tablesWithOrders>;

// (3) Extract and export the mapper function and type it using the above
export const mapMyTablesWithOrders = (results : TableWithOrdersQuery[]) =>
results.map((table) => ({
id: table.id,
name: table.name,
statusId: table.statusId,
status: table.tableStatus.name,
orders: table.orders.map((order) => ({
id: order.id,
name: order.menuItem.name,
price: order.menuItem.price,
orderTime: order.orderTime,
statusId: order.statusId,
status: order.status.name,
waiterId: order.waiterId,
waiter: order.waiter.name,
})),
}))

// This is the final type to be returned from the endpoint, which is exported
export type TableWithOrders = ReturnType<typeof mapMyTablesWithOrders>
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6a953fc

Please sign in to comment.