First working version of all examples including swagger.

This commit is contained in:
Mattias Hansson 2025-03-03 15:29:04 +01:00
parent 430a7ed162
commit 8d23b67592
12 changed files with 2101 additions and 0 deletions

55
example2b_swagger.yml Normal file
View file

@ -0,0 +1,55 @@
openapi: 3.0.3
info:
title: Swagger UserApi - OpenAPI 3.0
description: |-
This is a sample User API
termsOfService: http://swagger.io/terms/
contact:
email: apiteam@swagger.io
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
version: 1.0.11
externalDocs:
description: Find out more about Swagger
url: http://swagger.io
tags:
- name: user
description: Information about user
paths:
/api/users:
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
id:
type: number
name:
type: string
email:
format: email
type: string
age:
minimum: 0
type: number
required:
- id
- name
- email
- age
responses:
'200':
description: User created successfully
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
'400':
description: Invalid input

1394
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

27
package.json Normal file
View file

@ -0,0 +1,27 @@
{
"name": "nodejs-warsaw-xxi",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git@git.techserio.com:mattiashz/nodejs-warsaw-xxi.git"
},
"author": "hz",
"license": "ISC",
"dependencies": {
"@fastify/swagger": "^9.4.2",
"@fastify/swagger-ui": "^5.2.2",
"@fastify/type-provider-typebox": "^5.1.0",
"@sinclair/typebox": "^0.34.28",
"fastify": "^5.2.1",
"js-yaml": "^4.1.0"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.13.8"
}
}

57
src/example1.ts Normal file
View file

@ -0,0 +1,57 @@
import fastify from 'fastify';
const app = fastify({ logger: true });
interface User {
id: number;
name: string;
email: string;
age: number;
}
app.post<{ Body: User; }> ('/api/users', async (request, reply) => {
const user = request.body;
const canDrink = user.age >= 21;
const greeting = `Hello ${user.name.toUpperCase()}, your email ${user.email.toLowerCase()} has been recorded`;
return { user, canDrink, greeting };
});
const start = async () => {
try {
await app.listen({ port: 3000 });
console.log('Server running at http://localhost:3000');
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
start();
/*
Try making a POST request with this "deranged" JSON:
{
"id": "42",
"name": 12345,
"email": {"address": "bad@example.com"},
"age": "twenty-one"
}
What happens:
1. TypeScript is happy at compile time (we said body is User)
2. Fastify accepts this JSON as valid (no runtime validation)
3. Code fails when trying operations like toUpperCase() on a number
or comparing "twenty-one" >= 21
*/

46
src/example2.ts Normal file
View file

@ -0,0 +1,46 @@
import fastify from 'fastify';
import { Type } from '@sinclair/typebox';
import { TypeBoxValidatorCompiler } from '@fastify/type-provider-typebox';
const app = fastify({ logger: true });
app.setValidatorCompiler(TypeBoxValidatorCompiler);
interface User {
id: number;
name: string;
email: string;
age: number;
}
const TBUser = Type.Object({
id: Type.Number(),
name: Type.String(),
email: Type.String(),
age: Type.Number({ minimum: 0 })
});
app.post('/api/users', {
schema: {
body: TBUser
}
}, async (request, reply) => {
const user = request.body as User;
const canDrink = user.age >= 21;
const greeting = `Hello ${user.name.toUpperCase()}, your email ${user.email.toLowerCase()} has been recorded`;
return { user, canDrink, greeting };
});
const start = async () => {
try {
await app.listen({ port: 3000 });
console.log('Server running at http://localhost:3000');
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
start();

21
src/example2b.ts Normal file
View file

@ -0,0 +1,21 @@
import { Type } from '@sinclair/typebox';
import * as yaml from 'js-yaml';
// Define the TypeBox schema
const TBUser = Type.Object({
id: Type.Number(),
name: Type.String(),
email: Type.String({ format: 'email' }),
age: Type.Number({ minimum: 0 })
});
//console.log('JSON Schema:');
//console.log(JSON.stringify(TBUser, null, 2));
// Convert to YAML
const openApiYaml = yaml.dump(TBUser);
console.log('JSON Schema (YAML):');
console.log(openApiYaml);

46
src/example3.ts Normal file
View file

@ -0,0 +1,46 @@
import fastify from 'fastify';
import { Type, Static } from '@sinclair/typebox';
import { TypeBoxValidatorCompiler } from '@fastify/type-provider-typebox';
const app = fastify({ logger: true });
app.setValidatorCompiler(TypeBoxValidatorCompiler);
// Define TypeBox schema as the single source of truth
const UserSchema = Type.Object({
id: Type.Number(),
name: Type.String(),
email: Type.String(),
age: Type.Number({ minimum: 0 })
});
// Derive TypeScript type from the TypeBox schema
type User = Static<typeof UserSchema>;
app.post<{
Body: User; // Using the derived TypeScript type
}>('/api/users', {
schema: {
body: UserSchema // Using the TypeBox schema for validation
}
}, async (request, reply) => {
// No type assertion needed - Fastify + TypeBox understands the connection
const user = request.body;
const canDrink = user.age >= 21;
const greeting = `Hello ${user.name.toUpperCase()}, your email ${user.email.toLowerCase()} has been recorded`;
return { user, canDrink, greeting };
});
const start = async () => {
try {
await app.listen({ port: 3000 });
console.log('Server running at http://localhost:3000');
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
start();

89
src/example4.ts Normal file
View file

@ -0,0 +1,89 @@
import fastify from 'fastify';
import { Type, Static } from '@sinclair/typebox';
import { TypeBoxValidatorCompiler } from '@fastify/type-provider-typebox';
// Create Fastify instance
const app = fastify({ logger: true });
// Set up TypeBox validator
app.setValidatorCompiler(TypeBoxValidatorCompiler);
// Define TypeBox schema as the single source of truth
const UserSchema = Type.Object({
id: Type.Number(),
name: Type.String(),
email: Type.String(),
age: Type.Number({ minimum: 0 })
});
// Derive TypeScript type from the TypeBox schema
type User = Static<typeof UserSchema>;
// Response schema for better documentation
const ResponseSchema = Type.Object({
user: UserSchema,
canDrink: Type.Boolean(),
greeting: Type.String()
});
// Main function to set up the server
async function startServer() {
// Register Swagger plugin using dynamic import
await app.register(import('@fastify/swagger'), {
swagger: {
info: {
title: 'User API',
description: 'API for user operations with TypeBox validation',
version: '1.0.0'
},
tags: [
{ name: 'users', description: 'User related endpoints' }
]
}
});
// Register Swagger UI plugin using dynamic import
await app.register(import('@fastify/swagger-ui'), {
routePrefix: '/swagger',
uiConfig: {
docExpansion: 'list',
deepLinking: false
},
staticCSP: true
});
// Register our route
app.post('/api/users', {
schema: {
description: 'Create a new user',
tags: ['users'],
body: UserSchema,
response: {
200: ResponseSchema
}
}
}, async (request, reply) => {
const user = request.body as User;
const canDrink = user.age >= 21;
const greeting = `Hello ${user.name.toUpperCase()}, your email ${user.email.toLowerCase()} has been recorded`;
return { user, canDrink, greeting };
});
// Wait until all plugins are ready
await app.ready();
// Start the server
try {
await app.listen({ port: 3000 });
console.log('Server running at http://localhost:3000');
console.log('Swagger UI available at: http://localhost:3000/swagger');
} catch (err) {
app.log.error(err);
process.exit(1);
}
}
// Start the server
startServer();

117
src/example4b.ts Normal file
View file

@ -0,0 +1,117 @@
import fastify from 'fastify';
import { Type, Static } from '@sinclair/typebox';
import { TypeBoxValidatorCompiler } from '@fastify/type-provider-typebox';
// Create Fastify instance
const app = fastify({ logger: true });
// Set up TypeBox validator
app.setValidatorCompiler(TypeBoxValidatorCompiler);
// Define TypeBox schema as the single source of truth
const UserSchema = Type.Object({
id: Type.Number(),
name: Type.String(),
email: Type.String(),
age: Type.Number({ minimum: 0 })
});
// Derive TypeScript type from the TypeBox schema
type User = Static<typeof UserSchema>;
// Response schema for better documentation
const ResponseSchema = Type.Object({
user: UserSchema,
canDrink: Type.Boolean(),
greeting: Type.String()
});
// Main function to set up the server
async function startServer() {
// Register Swagger plugin using dynamic import
await app.register(import('@fastify/swagger'), {
swagger: {
info: {
title: 'User API',
description: 'API for user operations with TypeBox validation',
version: '1.0.0'
},
tags: [
{ name: 'users', description: 'User related endpoints' }
]
}
});
// Register Swagger UI plugin using dynamic import
await app.register(import('@fastify/swagger-ui'), {
routePrefix: '/swagger',
uiConfig: {
docExpansion: 'list',
deepLinking: false
},
staticCSP: true
});
// Register our route
app.post('/api/users', {
schema: {
description: 'Create a new user',
tags: ['users'],
body: UserSchema,
response: {
200: ResponseSchema
}
}
}, async (request, reply) => {
const user = request.body as User;
const canDrink = user.age >= 21;
const greeting = `Hello ${user.name.toUpperCase()}, your email ${user.email.toLowerCase()} has been recorded`;
return { user, canDrink, greeting };
});
app.get('/api/users', {
schema: {
description: 'Get all users',
tags: ['users'],
response: {
200: Type.Array(UserSchema)
}
}
}, async (request, reply) => {
// Sample data that follows our User type
const users: User[] = [
{
id: 1,
name: "John Doe",
email: "john@example.com",
age: 30
},
{
id: 2,
name: "Jane Smith",
email: "jane@example.com",
age: 25
}
];
return users;
});
// Wait until all plugins are ready
await app.ready();
// Start the server
try {
await app.listen({ port: 3000 });
console.log('Server running at http://localhost:3000');
console.log('Swagger UI available at: http://localhost:3000/swagger');
} catch (err) {
app.log.error(err);
process.exit(1);
}
}
// Start the server
startServer();

100
src/example4c.ts Normal file
View file

@ -0,0 +1,100 @@
import fastify from 'fastify';
import { Type, Static } from '@sinclair/typebox';
import { TypeBoxValidatorCompiler } from '@fastify/type-provider-typebox';
// Create Fastify instance
const app = fastify({ logger: true });
// Set up TypeBox validator
app.setValidatorCompiler(TypeBoxValidatorCompiler);
// Define TypeBox schema as the single source of truth
const UserSchema = Type.Object({
id: Type.Number(),
name: Type.String(),
email: Type.String(),
age: Type.Number({ minimum: 0 })
}, { $id: 'User' });
// Derive TypeScript type from the TypeBox schema
type User = Static<typeof UserSchema>;
// Response schema for better documentation
const ResponseSchema = Type.Object({
user: Type.Ref(UserSchema),
canDrink: Type.Boolean(),
greeting: Type.String()
}, { $id: 'UserResponse' });
// Main function to set up the server
async function startServer() {
// Register schemas with Fastify
app.addSchema(UserSchema);
app.addSchema(ResponseSchema);
// Register Swagger plugin using dynamic import
await app.register(import('@fastify/swagger'), {
swagger: {
info: {
title: 'User API',
description: 'API for user operations with TypeBox validation',
version: '1.0.0'
},
consumes: ['application/json'],
produces: ['application/json'],
tags: [
{ name: 'users', description: 'User related endpoints' }
],
// Extract schemas to definitions section
definitions: {
User: UserSchema as any,
UserResponse: ResponseSchema as any
}
}
});
// Register Swagger UI plugin using dynamic import
await app.register(import('@fastify/swagger-ui'), {
routePrefix: '/swagger',
uiConfig: {
docExpansion: 'list',
deepLinking: false
},
staticCSP: true
});
// Register our route
app.post('/api/users', {
schema: {
description: 'Create a new user',
tags: ['users'],
body: { $ref: 'User#' },
response: {
200: { $ref: 'UserResponse#' }
}
}
}, async (request, reply) => {
const user = request.body as User;
const canDrink = user.age >= 21;
const greeting = `Hello ${user.name.toUpperCase()}, your email ${user.email.toLowerCase()} has been recorded`;
return { user, canDrink, greeting };
});
// Wait until all plugins are ready
await app.ready();
// Start the server
try {
await app.listen({ port: 3000 });
console.log('Server running at http://localhost:3000');
console.log('Swagger UI available at: http://localhost:3000/swagger');
} catch (err) {
app.log.error(err);
process.exit(1);
}
}
// Start the server
startServer();

131
src/example4d.ts Normal file
View file

@ -0,0 +1,131 @@
import fastify from 'fastify';
import { Type, Static } from '@sinclair/typebox';
import { TypeBoxValidatorCompiler } from '@fastify/type-provider-typebox';
// Create Fastify instance
const app = fastify({ logger: true });
// Set up TypeBox validator
app.setValidatorCompiler(TypeBoxValidatorCompiler);
// Define TypeBox schema as the single source of truth
const UserSchema = Type.Object({
id: Type.Number(),
name: Type.String(),
email: Type.String(),
age: Type.Number({ minimum: 0 })
}, { $id: 'User' });
// Derive TypeScript type from the TypeBox schema
type User = Static<typeof UserSchema>;
// Response schema for better documentation
const ResponseSchema = Type.Object({
user: UserSchema,
canDrink: Type.Boolean(),
greeting: Type.String()
}, { $id: 'UserResponse' });
// Main function to set up the server
async function startServer() {
// Register schemas with Fastify
app.addSchema(UserSchema);
app.addSchema(ResponseSchema);
// Register Swagger plugin using dynamic import
await app.register(import('@fastify/swagger'), {
openapi: {
info: {
title: 'User API',
description: 'API for user operations with TypeBox validation',
version: '1.0.0'
},
tags: [
{ name: 'users', description: 'User related endpoints' }
]
},
refResolver: {
buildLocalReference: (json, baseUri, fragment, i) => {
// Use $id property from schema
if (json.$id) {
return `#/components/schemas/${json.$id}`;
}
// Fall back to default behavior
return `#/components/schemas/${fragment[1]}${i}`;
}
}
});
// Register Swagger UI plugin using dynamic import
await app.register(import('@fastify/swagger-ui'), {
routePrefix: '/swagger',
uiConfig: {
docExpansion: 'list',
deepLinking: false
},
staticCSP: true
});
// Register our route
app.post('/api/users', {
schema: {
description: 'Create a new user',
tags: ['users'],
body: UserSchema,
response: {
200: ResponseSchema
}
}
}, async (request, reply) => {
const user = request.body as User;
const canDrink = user.age >= 21;
const greeting = `Hello ${user.name.toUpperCase()}, your email ${user.email.toLowerCase()} has been recorded`;
return { user, canDrink, greeting };
});
app.get('/api/users', {
schema: {
description: 'Get all users',
tags: ['users'],
response: {
200: Type.Array(UserSchema)
}
}
}, async (request, reply) => {
// Sample data that follows our User type
const users: User[] = [
{
id: 1,
name: "John Doe",
email: "john@example.com",
age: 30
},
{
id: 2,
name: "Jane Smith",
email: "jane@example.com",
age: 25
}
];
return users;
});
// Wait until all plugins are ready
await app.ready();
// Start the server
try {
await app.listen({ port: 3000 });
console.log('Server running at http://localhost:3000');
console.log('Swagger UI available at: http://localhost:3000/swagger');
} catch (err) {
app.log.error(err);
process.exit(1);
}
}
// Start the server
startServer();

18
tsconfig.json Normal file
View file

@ -0,0 +1,18 @@
{
"compilerOptions": {
"esModuleInterop": true,
"lib": [
"ES2020.Promise",
"DOM"
],
"module": "commonjs",
"noImplicitAny": true,
"outDir": "build",
"rootDir": "src",
"sourceMap": true,
"target": "es2017"
},
"exclude": [
"node_modules"
]
}