Last active
January 14, 2023 18:28
-
-
Save jonerer/52493a72b8cf48359789c5f60595061f to your computer and use it in GitHub Desktop.
API Constructor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Concept for generating boilerplate for API | |
=== | |
One issue is that the input and output parts are TS, and the others could be done with a DSL. (in order to not have to re-invent zod basically). | |
I used typescript interfaces for DTOs and zod for input, and came up with something like this: | |
For the DSL it could look like so: | |
=== | |
types ts```{ | |
CommentUpdateBody = z.object({ | |
text: z.string() | |
}) | |
interface CommentDto { | |
id: number | |
text: string | |
updates: number | |
} | |
}``` | |
route "/comments" { | |
// "model" here is used to tell the endpoint what to populate the context with for ":id" | |
model comment | |
// response dto will be pluralized for index paths | |
// response should be serialized into dto automatically (shaving off unwanted fields etc) | |
response CommentDto | |
get "/" | |
post "/:id" { | |
// default name "update" | |
body CommentUpdateBody | |
} | |
} | |
Perhaps "model" could be skipped, if the endpoint path is something like "/:commentId" | |
=== | |
For the "update" endpoint this should generate a file with some boilerplate and some types. | |
Types like | |
interface EndpointCommentsContext { | |
params: { | |
id: string | |
comment: prisma.Comment | |
} | |
body: z.infer<typeof CommentUpdateBody> | |
req: Request | |
res: Response | |
} | |
export type EndpointCommentsUpdate = ( | |
ctx: EndpointCommentsContext | |
) => Promise<CommentDto> | |
That can be used like | |
export const update: EndpointCommentsUpdate = async (ctx) => { | |
const updated = await prisma.comment.update({ | |
data: { | |
updates: ctx.params.comment.updates + 1, | |
text: ctx.body.text, | |
}, | |
where: { | |
id: ctx.params.comment.id, | |
}, | |
}) | |
return updated | |
} | |
=== | |
This could be supported by some generated boilerplate like | |
export function toCommentDto(retval: CommentDto): CommentDto { | |
return { | |
id: +retval.id, | |
text: "" + retval.text, | |
updates: +retval.updates, | |
} | |
} | |
export const Register = (router: Router, prisma: prisma.PrismaClient) => { | |
router.post("/comments/:id", async function (req, res) { | |
try { | |
// param | |
const id = req.params.id | |
const comment = await prisma.comment.findFirst({ | |
where: { | |
id: +id, | |
}, | |
}) | |
if (!comment) { | |
res.status(404).send("404 comment not found") | |
return | |
} | |
const zodThing = CommentUpdateBody.safeParse(req.body) | |
if (!zodThing.success) { | |
res.status(400).send("Invalid request body") | |
return | |
} | |
const ctx: EndpointCommentsContext = { | |
params: { | |
comment, | |
id, | |
}, | |
body: zodThing.data, | |
req, | |
res, | |
} | |
const retval = await CommentsUpdate(ctx) | |
const dtod = toCommentDto(retval) | |
res.status(200).send(dtod) | |
} catch (e) { | |
res.status(500).send("Internal server error") | |
return | |
} | |
}) | |
} | |
=== | |
I also wrote a parser for this in "Peggy" https://peggyjs.org/online.html | |
start | |
= Statements | |
Statements | |
= Statement* | |
Statement = | |
TypesBlock | |
/ RouteBlock | |
/ CommentLine | |
/ __ | |
RouteBlock | |
= "route" __ "\"" path:[/a-z]+ "\"" _ "{" _ | |
stmts:RouteStatements* | |
_ "}" { return `{type: "route", path: "${path.join('')}"}, statements: [${stmts.join(",")}"` } | |
RouteStatements | |
= RouteStatement+ | |
RouteStatement | |
= ModelStatement | |
/ ResponseStatement | |
/ EndpointBlock | |
/ EndpointShorthand | |
/ __ | |
EndpointShorthand | |
= verb:("get" / "post") __ "\"" path:[:/a-z]+ "\"" | |
{ return `{ type: "endpoint", verb: "${verb}", path: "${path.join("")}", body: [] }` } | |
EndpointBlock | |
= verb:("get" / "post") __ "\"" path:[:/a-z]+ "\"" _ "{" _ | |
body:EndpointBlockStatements | |
_ "}" { return `{ type: "endpoint", verb: "${verb}", path: "${path.join("")}", body: [${body}] }` } | |
EndpointBlockStatements = EndpointBlockStatement+ | |
EndpointBlockStatement | |
= BodyStatement | |
/ CommentLine | |
/ __ | |
BodyStatement | |
= "body" __ name:[a-zA-Z]+ | |
{ return `{ type: "body", name: ${name.join("")} }` } // <--- jobbar här! | |
ModelStatement | |
= "model" __ name:[a-zA-Z]+ | |
{ return `{type: "model", name: "${name.join('')}" }` } | |
ResponseStatement | |
= "response" __ name:[a-zA-Z]+ | |
{ return `{type: "response", name: "${name.join('')}" }` } | |
// https://stackoverflow.com/questions/39604207/peg-js-get-any-text-between-and | |
TypesBlock "Types block" | |
= "types" _ "ts```" _ body:TextUntilTerminator _ "```" { return body.join(""); } | |
// { return "minbody:" + body } | |
TextUntilTerminator | |
= x:(&HaveTerminatorAhead .)* { return x.map(y => y[1]) } | |
HaveTerminatorAhead | |
= . (!"```" .)* "```" | |
CommentLine | |
= [ \t]* "//" [^\n]* "\n" | |
{} | |
__ "whitespace" | |
= [ \t\n\r]+ | |
{return ''} | |
_ "maybe whitespace" | |
= [ \t\n\r]* | |
{return ''} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is assuming a prisma model like
model Comment {
id Int @id
updates Int
originalText String
text String
}