Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(repo): add fastify api via netlify functions #284

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
"jest": true
},
"rules": {}
},
{
"files": "*.json",
"parser": "jsonc-eslint-parser",
"rules": {}
}
]
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ testem.log
# System Files
.DS_Store
Thumbs.db

# Local Netlify folder
.netlify
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"eslint.validate": ["json"]
}
10 changes: 10 additions & 0 deletions apps/cart-api-e2e/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
}
]
}
19 changes: 19 additions & 0 deletions apps/cart-api-e2e/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable */
export default {
displayName: 'cart-api-e2e',
preset: '../../jest.preset.js',
globalSetup: '<rootDir>/src/support/global-setup.ts',
globalTeardown: '<rootDir>/src/support/global-teardown.ts',
setupFiles: ['<rootDir>/src/support/test-setup.ts'],
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
},
],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/cart-api-e2e',
};
22 changes: 22 additions & 0 deletions apps/cart-api-e2e/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "cart-api-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"implicitDependencies": ["cart-api"],
"targets": {
"e2e": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{e2eProjectRoot}"],
"options": {
"jestConfig": "apps/cart-api-e2e/jest.config.ts",
"passWithNoTests": true
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/cart-api-e2e/**/*.{js,ts}"]
}
}
}
}
10 changes: 10 additions & 0 deletions apps/cart-api-e2e/src/cart-api/cart-api.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import axios from 'axios';

describe('GET /', () => {
it('should return a message', async () => {
const res = await axios.get(`/`);

expect(res.status).toBe(200);
expect(res.data).toEqual({ message: 'Hello API' });
});
});
10 changes: 10 additions & 0 deletions apps/cart-api-e2e/src/support/global-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* eslint-disable */
var __TEARDOWN_MESSAGE__: string;

module.exports = async function () {
// Start services that that the app needs to run (e.g. database, docker-compose, etc.).
console.log('\nSetting up...\n');

// Hint: Use `globalThis` to pass variables to global teardown.
globalThis.__TEARDOWN_MESSAGE__ = '\nTearing down...\n';
};
7 changes: 7 additions & 0 deletions apps/cart-api-e2e/src/support/global-teardown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* eslint-disable */

module.exports = async function () {
// Put clean up logic here (e.g. stopping services, docker-compose, etc.).
// Hint: `globalThis` is shared between setup and teardown.
console.log(globalThis.__TEARDOWN_MESSAGE__);
};
10 changes: 10 additions & 0 deletions apps/cart-api-e2e/src/support/test-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* eslint-disable */

import axios from 'axios';

module.exports = async function () {
// Configure axios for tests to use.
const host = process.env.HOST ?? 'localhost';
const port = process.env.PORT ?? '3000';
axios.defaults.baseURL = `http://${host}:${port}`;
};
13 changes: 13 additions & 0 deletions apps/cart-api-e2e/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"esModuleInterop": true
}
}
9 changes: 9 additions & 0 deletions apps/cart-api-e2e/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["jest.config.ts", "src/**/*.ts"]
}
18 changes: 18 additions & 0 deletions apps/cart-api/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
11 changes: 11 additions & 0 deletions apps/cart-api/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: 'cart-api',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/cart-api',
};
80 changes: 80 additions & 0 deletions apps/cart-api/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"name": "cart-api",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/cart-api/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/esbuild:esbuild",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"platform": "node",
"outputPath": "dist/apps/cart-api",
"format": ["cjs"],
"bundle": false,
"main": "apps/cart-api/src/main.ts",
"tsConfig": "apps/cart-api/tsconfig.app.json",
"assets": ["apps/cart-api/src/assets"],
"generatePackageJson": true,
"esbuildOptions": {
"sourcemap": true,
"outExtension": {
".js": ".js"
}
}
},
"configurations": {
"development": {},
"production": {
"esbuildOptions": {
"sourcemap": false,
"outExtension": {
".js": ".js"
}
}
}
}
},
"serve": {
"executor": "@nx/js:node",
"defaultConfiguration": "development",
"options": {
"buildTarget": "cart-api:build"
},
"configurations": {
"development": {
"buildTarget": "cart-api:build:development"
},
"production": {
"buildTarget": "cart-api:build:production"
}
}
},
"serve-functions": {
"command": "BROWSER=false netlify dev --functions=apps/cart-api/src/functions"
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/cart-api/**/*.ts"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/cart-api/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
}
},
"tags": []
}
20 changes: 20 additions & 0 deletions apps/cart-api/src/app/app.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Fastify, { FastifyInstance } from 'fastify';
import { app } from './app';

describe('GET /', () => {
let server: FastifyInstance;

beforeEach(() => {
server = Fastify();
server.register(app);
});

it('should respond with a message', async () => {
const response = await server.inject({
method: 'GET',
url: '/',
});

expect(response.json()).toEqual({ message: 'Hello API' });
});
});
29 changes: 29 additions & 0 deletions apps/cart-api/src/app/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// import * as path from 'path';
import { FastifyInstance } from 'fastify';
// import AutoLoad from '@fastify/autoload';
import { productsRoutes } from '@nx-example/products/product-routes';

/* eslint-disable-next-line */
export interface AppOptions {}

export async function app(fastify: FastifyInstance, opts: AppOptions) {
// Place here your custom code!

// Do not touch the following lines

// This loads all plugins defined in plugins
// those should be support plugins that are reused
// // through your application
// fastify.register(AutoLoad, {
// dir: path.join(__dirname, 'plugins'),
// options: { ...opts },
// });

// // This loads all plugins defined in routes
// // define your routes in one of these
// fastify.register(AutoLoad, {
// dir: path.join(__dirname, 'routes'),
// options: { ...opts },
// });
fastify.register(productsRoutes, { prefix: '/api' });
}
12 changes: 12 additions & 0 deletions apps/cart-api/src/app/plugins/cors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { FastifyInstance } from 'fastify';
import fp from 'fastify-plugin';
import cors from '@fastify/cors';

/**
* This plugins adds some utilities to handle http errors
*
* @see https://github.com/fastify/fastify-sensible
*/
export default fp(async function (fastify: FastifyInstance) {
fastify.register(cors);
});
12 changes: 12 additions & 0 deletions apps/cart-api/src/app/plugins/sensible.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { FastifyInstance } from 'fastify';
import fp from 'fastify-plugin';
import sensible from '@fastify/sensible';

/**
* This plugins adds some utilities to handle http errors
*
* @see https://github.com/fastify/fastify-sensible
*/
export default fp(async function (fastify: FastifyInstance) {
fastify.register(sensible);
});
26 changes: 26 additions & 0 deletions apps/cart-api/src/app/routes/root.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { FastifyInstance, FastifyRequest } from 'fastify';
import { randomUUID } from 'crypto';
import { products } from '@nx-example/shared/product/data';

export default async function (fastify: FastifyInstance) {
fastify.post(
'/api/checkout',
async (
request: FastifyRequest<{
Body: { productId: string; quanntity: number }[];
}>
) => {
const items = request.body;
console.log(request.body);
const price = items.reduce((acc, item) => {
const product = products.find((p) => p.id === item.productId);
return acc + product.price * item.quanntity;
}, 0);

// gotta think real hard
await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));

return { success: true, orderId: randomUUID(), total: price };
}
);
}
Empty file.
Empty file.
9 changes: 9 additions & 0 deletions apps/cart-api/src/functions/api/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// @ts-expect-error - when running serverless this will have a default export
import init from '../../main';
import { Handler } from '@netlify/functions';
import awsLambdaFastify from '@fastify/aws-lambda';

// Setup serverless functionality for api.
// Note: Netlify deploys this function at the endpoint /.netlify/functions/api
// so we prefix all routes with /.netlify/functions to ensure fastify still works correctly when running with netlify
export const handler: Handler = awsLambdaFastify(init('/.netlify/functions'));
Loading