Skip to content

Commit ac262e5

Browse files
authored
Merge pull request #3 from StatelessStudio/v1.1.0
V1.1.0
2 parents 9e6f4ad + 176def3 commit ac262e5

14 files changed

Lines changed: 592 additions & 129 deletions

README.md

Lines changed: 5 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# api-machine - REST API Server Framework
1+
# API-Machine
22

33
A lightweight, TypeScript-first REST API framework built on Express with a class-based routing architecture.
44

@@ -85,83 +85,17 @@ The [`examples/`](examples/) directory contains comprehensive examples:
8585
- Request body validation
8686
- Structured error responses
8787

88-
## Configuration
89-
90-
### Server Options
91-
#### port `number = 5000`
92-
93-
The port number on which the server will listen for incoming requests.
94-
95-
#### maxPayloadSizeMB `number = 10`
96-
97-
Maximum size in megabytes for JSON request payloads that the server will accept.
98-
99-
#### maxUrlEncodedSizeMB `number = 1`
100-
101-
Maximum size in megabytes for URL-encoded request payloads that the server will accept.
102-
103-
#### log `LogInterface = console`
104-
105-
Custom logger interface for handling server logging (e.g. `ts-tiny-log`). Must implement the LogInterface contract.
106-
107-
#### securityHeaders `SecurityHeadersOptions`
108-
109-
Configuration for HTTP security headers. By default, api-machine is **secure by default** with:
110-
- X-Powered-By header removed (prevents server fingerprinting)
111-
- X-Content-Type-Options: nosniff (prevents MIME sniffing)
112-
- X-Frame-Options: DENY (prevents clickjacking)
113-
- X-XSS-Protection: 1; mode=block (legacy XSS protection)
114-
115-
See **[Security Headers Documentation](docs/security-headers.md)** for detailed configuration options and best practices.
116-
11788
### Example with Options
11889

11990
```typescript
120-
const server = new MyServer({
91+
const server = new MyApiServer({
12192
port: 8080,
12293
maxPayloadSizeMB: 20,
12394
maxUrlEncodedSizeMB: 2,
12495
log: myCustomLogger
12596
});
12697
```
12798

128-
## API Summary
129-
130-
### RestServer
131-
132-
Abstract class for creating REST API servers.
133-
134-
**Methods:**
135-
- `start()`: Starts the server
136-
- `stop()`: Stops the server
137-
138-
139-
### BaseApiRouter
140-
141-
Abstract class for creating route groups.
142-
143-
**Properties:**
144-
- `path`: The base path for the router (e.g., `/api`)
145-
146-
**Methods:**
147-
- `routes()`: Abstract method to define endpoints (must be implemented)
148-
149-
### BaseApiEndpoint
150-
151-
Abstract class for creating API endpoints.
152-
153-
**Properties:**
154-
- `path`: The endpoint path (default: `''`)
155-
- `method`: The HTTP method (default: `GET`). Can be:
156-
- `EndpointMethods.GET`
157-
- `EndpointMethods.POST`
158-
- `EndpointMethods.PATCH`
159-
- `EndpointMethods.PUT`
160-
- `EndpointMethods.DELETE`
161-
162-
**Methods:**
163-
- `handle(request, response, next)`: Abstract method to handle requests (must be implemented)
164-
16599
#### Using Different HTTP Methods
166100

167101
api-machine provides convenience classes for each HTTP method with appropriate default status codes:
@@ -227,55 +161,11 @@ class DeleteUserEndpoint extends DeleteEndpoint {
227161
}
228162
```
229163

230-
**Available Endpoint Classes:**
231-
- `GetEndpoint` - GET requests (200 OK)
232-
- `PostEndpoint` - POST requests (201 Created)
233-
- `PutEndpoint` - PUT requests (200 OK)
234-
- `PatchEndpoint` - PATCH requests (200 OK)
235-
- `DeleteEndpoint` - DELETE requests (204 No Content)
236-
- `HealthCheckEndpoint` - Pre-built health check endpoint (GET /health)
237-
238-
You can also use `BaseApiEndpoint` and manually set the `method` and `statusCode` properties if needed for custom behavior.
239-
240164
#### Pre-Built Endpoints
241165

242166
##### HealthCheckEndpoint
243167

244-
A ready-to-use health check endpoint that returns system status information. Simply include it in your router:
245-
246-
```typescript
247-
import { BaseApiRouter, HealthCheckEndpoint } from 'api-machine';
248-
249-
class MyRouter extends BaseApiRouter {
250-
override path = '/api';
251-
252-
async routes() {
253-
return [
254-
HealthCheckEndpoint, // Available at GET /api/health
255-
// ... other endpoints
256-
];
257-
}
258-
}
259-
```
260-
261-
**Response Format:**
262-
```json
263-
{
264-
"status": "ok",
265-
"timestamp": "2025-11-08T12:00:00.000Z",
266-
"uptime": 123.45,
267-
"environment": "development"
268-
}
269-
```
270-
271-
**Customizing the Path:**
272-
```typescript
273-
class CustomHealthCheck extends HealthCheckEndpoint {
274-
override path = '/status'; // Available at GET /api/status
275-
}
276-
```
277-
278-
For advanced usage, extending the health check with custom checks, and deployment examples (Kubernetes, Docker, monitoring), see the **[Health Check Endpoint Documentation](docs/health-check-endpoint.md)**.
168+
A ready-to-use health check endpoint that returns system status information. Simply include it in your router. For advanced usage, extending the health check with custom checks, and deployment examples (Kubernetes, Docker, monitoring), see the **[Health Check Endpoint Documentation](docs/health-check-endpoint.md)**.
279169

280170
## Error Handling
281171

@@ -303,7 +193,7 @@ class GetUserEndpoint extends GetEndpoint {
303193
```
304194

305195
**Key Features:**
306-
- 29 built-in error classes covering HTTP status codes 400-451
196+
- Built-in error classes covering HTTP status codes 400-451
307197
- Automatic JSON error responses with timestamps
308198
- Support for custom headers (e.g., `WWW-Authenticate`, `Retry-After`)
309199
- Optional `details` field for additional context
@@ -376,7 +266,7 @@ See **[Authentication Documentation](docs/authentication.md)** for complete usag
376266

377267
## Middleware
378268

379-
Routers and endpoints support Express-style middleware for logging, validation, and more. See [Middleware Support](docs/middleware.md) for usage and examples.
269+
Routers and endpoints support Express middleware for logging, validation, and more. See [Middleware Support](docs/middleware.md) for usage and examples.
380270

381271
## Contributing & Development
382272

examples/complete-example/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const server = new MyServer({
3737
port: 3000,
3838
maxPayloadSizeMB: 10,
3939
log: customLogger,
40+
swaggerEnabled: true,
4041
});
4142

4243
server

examples/complete-example/users/create-user-endpoint.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ApiRequest, ApiResponse, PostEndpoint } from '../../../src/index';
22
import { usersRepo, User } from './users-repository';
3-
import { ObjectSanitizer, EmailValidator } from 'valsan';
3+
import { ObjectSanitizer, EmailValidator, IntegerValidator } from 'valsan';
44
import { NameValSan } from './name-valsan';
55

66
/**
@@ -11,11 +11,29 @@ import { NameValSan } from './name-valsan';
1111
export class CreateUserEndpoint extends PostEndpoint {
1212
override path = '/';
1313

14+
override bodyExample = {
15+
name: 'John Doe',
16+
email: 'john@example.com',
17+
};
18+
1419
override body = new ObjectSanitizer({
1520
name: new NameValSan(),
1621
email: new EmailValidator(),
1722
});
1823

24+
override responseExample = {
25+
id: 3,
26+
name: 'John Doe',
27+
email: 'john@example.com',
28+
created: new Date(),
29+
};
30+
31+
override response = new ObjectSanitizer({
32+
id: new IntegerValidator(),
33+
name: new NameValSan(),
34+
email: new EmailValidator(),
35+
});
36+
1937
async handle(request: ApiRequest, response: ApiResponse) {
2038
const { name, email } = request.body;
2139

examples/complete-example/users/get-user-endpoint.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { ApiRequest, ApiResponse, GetEndpoint } from '../../../src/index';
22
import { NotFoundError } from '../../../src/error';
33
import { usersRepo } from './users-repository';
4+
import { ObjectSanitizer, IntegerValidator, EmailValidator } from 'valsan';
5+
import { NameValSan } from './name-valsan';
46

57
/**
68
* Complete Example - Get User Endpoint
@@ -11,6 +13,19 @@ import { usersRepo } from './users-repository';
1113
export class GetUserEndpoint extends GetEndpoint {
1214
override path = '/:id';
1315

16+
override responseExample = {
17+
id: 1,
18+
name: 'Alice',
19+
email: 'alice@example.com',
20+
created: new Date('2023-01-01'),
21+
};
22+
23+
override response = new ObjectSanitizer({
24+
id: new IntegerValidator(),
25+
name: new NameValSan(),
26+
email: new EmailValidator(),
27+
});
28+
1429
async handle(request: ApiRequest, response: ApiResponse) {
1530
const userId = parseInt(request.params['id'], 10);
1631
const user = usersRepo[userId];

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "api-machine",
3-
"version": "1.0.1",
3+
"version": "1.1.0",
44
"description": "api-machine",
55
"private": "true",
66
"typescript-template": {

src/oas/oas-endpoint-component-converter.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ export class OasEndpointComponentConverter {
2222
});
2323
}
2424

25+
const responseSanitizer = endpoint.response;
26+
27+
if (responseSanitizer) {
28+
this.addSchema({
29+
name: `${endpoint.name}Response`,
30+
sanitizer: responseSanitizer,
31+
example: endpoint.responseExample,
32+
});
33+
}
34+
2535
return this.schemas;
2636
}
2737

src/oas/oas-endpoint-converter.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
PathItemObject,
44
ParameterObject,
55
RequestBodyObject,
6+
ResponsesObject,
67
} from 'auto-oas/oas/v3.1';
78

89
import { BaseApiEndpoint } from '../router';
@@ -60,9 +61,28 @@ export class OasEndpointConverter {
6061
// Use statusCode for response
6162
const status = endpoint.statusCode;
6263

63-
const responses = {
64-
[status]: { description: 'Success' },
65-
};
64+
const responses: ResponsesObject = {};
65+
66+
// Generate success response with schema if available
67+
const responseSanitizer = endpoint.response;
68+
if (responseSanitizer && responseSanitizer.schema) {
69+
responses[status] = {
70+
description: 'Success',
71+
content: {
72+
'application/json': {
73+
schema: {
74+
$ref:
75+
'#/components/schemas/' +
76+
endpoint.getName() +
77+
'Response',
78+
},
79+
},
80+
},
81+
};
82+
}
83+
else {
84+
responses[status] = { description: 'Success' };
85+
}
6686

6787
const errors = endpoint.getErrors();
6888
for (const error in errors) {

src/router/endpoint.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ export abstract class BaseApiEndpoint extends BaseApiRoute {
4949
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5050
public headersExample?: any;
5151

52+
public response?: ObjectSanitizer;
53+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
54+
public responseExample?: any;
55+
5256
public getErrors(): { [key: string]: HTTPError } {
5357
return {
5458
parse: new BadRequestError(),

src/router/endpoints/health-check-endpoint.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
1-
import { ApiRequest, ApiResponse } from '../endpoint';
1+
import {
2+
Iso8601TimestampValSan,
3+
MinLengthValidator,
4+
ObjectSanitizer,
5+
StringToNumberValSan,
6+
} from 'valsan';
27
import { GetEndpoint } from './get-endpoint';
38

49
export class HealthCheckEndpoint extends GetEndpoint {
510
override path = '/health';
611

7-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
8-
async handle(_request: ApiRequest, _response: ApiResponse) {
12+
override responseExample = {
13+
status: 'ok',
14+
timestamp: new Date().toISOString(),
15+
uptime: 12345,
16+
environment: 'development',
17+
};
18+
19+
override response = new ObjectSanitizer({
20+
status: new MinLengthValidator(),
21+
timestamp: new Iso8601TimestampValSan(),
22+
uptime: new StringToNumberValSan(),
23+
environment: new MinLengthValidator(),
24+
});
25+
26+
async handle() {
927
return {
1028
status: await this.getStatus(),
1129
timestamp: await this.getTimestamp(),

0 commit comments

Comments
 (0)