We're not super far along on the front end, but I want to get it talking to a server and database so we can make sure the vertical slice is running nice.
I decided to set the server up with NestJS (not to be confused with Next.js) instead of Express since this is a learning project and I've used Express in the past. It also seems like NestJS is set up with TypeScript, is opinionated, and uses some patterns I'm less familiar with. I like those things!
Since there are design patterns I'm less familiar with such as decorators and dependency injection. It's almost the holidays and NestJS decorates everything so it seemed like a good fit. I've used decorators in the past, but it's been a few years. Unless you count React's higher-order components
One goal of the server is to also generate openAPI specs automatically and use those to generate TypeScript types that can be consumed by other repos. In the past I've run into some API interface issues from inconsistent types so I'd like a single source of truth.
NestJS supports automatic validation on the endpoints. In NestJS you define Data Transfer Objects (DTOs) as classes which are used to represent your requests.
That means I can create a class object, use NestJS's class-validator library to add decorators so it looks like this
import { IsString, IsInt, ValidateNested } from 'class-validator'
class Answer {
@IsString()
value: string
@IsString()
type: 'text' | 'image'
@IsString()
exact: boolean
}
export class CreateHuntDto {
@ValidateNested()
answer: Answer
@IsString()
clue: string
@IsString()
description?: string
@IsInt()
latitude: number
@IsInt()
longitude: number
@IsString()
name: string
}
Then use the SwaggerModule to generate an openAPI spec
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'
...
// This config goes inside the boostrap function in main.ts
const config = new DocumentBuilder()
.setTitle('Scavenger Hunt API')
.setDescription('API endpoints for building a scavenger hunt')
.setVersion('0.1')
.addTag('scavenger hunt')
.build();
const swaggerOptions = {
autoTagControllers: true,
};
const swaggerDocument = SwaggerModule.createDocument(
app,
config,
swaggerOptions,
);
// This lets us view the api endpoint when server is running
SwaggerModule.setup('api', app, swaggerDocument);
// This saves the spec as a file so we can use it to generate TypeScript next!
const outputPath = path.resolve(__dirname, 'swagger-spec.json');
fs.writeFileSync(outputPath, JSON.stringify(swaggerDocument, null, 2));
...
And then we build our types with openapi-typescript. Here's a little snippet:
//src/types/api.d.ts
export interface components {
schemas: {
Answer: {
value: string;
type: Record<string, never>;
exact: boolean;
};
CreateHuntDto: {
answer: components["schemas"]["Answer"];
clue: string;
description?: string;
latitude: number;
longitude: number;
name: string;
};
Next steps:
I know I know. Where's the AI? I asked Copilot Chat a few things here and there, but I was also reading the documentation on NextJS directly.
"Why?" you may ask? When I asked Copilot some of the above questions Copilot was returning the docs almost verbatim so I just cut out the middleman until I ran into a confusing implementation detail or next step.
A next step like we have in the list above!
I know I don't have to be so verbose, but it's hard to break that habit.
It seems pretty straightforward. You make a types package inside your package.
mkdir types cd types npm init -y
Now I can test pushing my... wait...
I haven't done my initial commit yet. Not a giant deal, but now it's gonna be a bit large for an initial commit and I can't link y'all to the steps as easy. Sorry about that. Let's fix it!