Skip to content

Commit

Permalink
feat: add fastify app with basic CRUD functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
이은상 committed Dec 18, 2024
1 parent de52cef commit 9a3c9ee
Show file tree
Hide file tree
Showing 16 changed files with 426 additions and 0 deletions.
42 changes: 42 additions & 0 deletions .github/workflows/3_middlewares_4_fastify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: 3_middlewares_4_fastify

on:
push:
branches:
- master
paths:
- 3_middlewares/4_fastify/**
workflow_dispatch:

env:
APP_NAME: middleware_fastify
PROJECT_DIR: 3_middlewares/4_fastify

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/[email protected]

- id: lower-credentials
name: Lower Credentials
run: |
echo "actor=${GITHUB_ACTOR@L}" >> $GITHUB_OUTPUT
echo "repository=${GITHUB_REPOSITORY@L}" >> $GITHUB_OUTPUT
- name: Login to GitHub Container Registry
uses: docker/[email protected]
with:
registry: ghcr.io
username: ${{ steps.lower-credentials.outputs.actor }}
password: ${{ secrets.GHCR_PAT }}

- name: Build Docker image
run: |
cd ${{ env.PROJECT_DIR }} && \
docker build -t ghcr.io/${{ steps.lower-credentials.outputs.repository }}/${{ env.APP_NAME }}:latest .
- name: Push Docker image to GHCR
run: |
docker push ghcr.io/${{ steps.lower-credentials.outputs.repository }}/${{ env.APP_NAME }}:latest
20 changes: 20 additions & 0 deletions 3_middlewares/3_express_js/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ app.use(bodyParser.json());
let items = [];
let currentId = 1;

/**
* Create a new item
* @example http POST :3000/items name="Item1" description="This is Item1"
*/
app.post('/items', (req, res) => {
const { name, description } = req.body;
if (!name || !description) {
Expand All @@ -19,10 +23,18 @@ app.post('/items', (req, res) => {
res.status(201).json(newItem);
});

/**
* Read all items
* @example http :3000/items
*/
app.get('/items', (req, res) => {
res.json(items);
});

/**
* Read a single item by ID
* @example http :3000/items/1
*/
app.get('/items/:id', (req, res) => {
const id = parseInt(req.params.id, 10);
const item = items.find(i => i.id === id);
Expand All @@ -32,6 +44,10 @@ app.get('/items/:id', (req, res) => {
res.json(item);
});

/**
* Update an existing item by ID
* @example http PUT :3000/items/1 name="Item1 - Updated"
*/
app.put('/items/:id', (req, res) => {
const id = parseInt(req.params.id, 10);
const { name, description } = req.body;
Expand All @@ -44,6 +60,10 @@ app.put('/items/:id', (req, res) => {
res.json(item);
});

/**
* Delete an item by ID
* @example http DELETE :3000/items/1
*/
app.delete('/items/:id', (req, res) => {
const id = parseInt(req.params.id, 10);
const index = items.findIndex(i => i.id === id);
Expand Down
26 changes: 26 additions & 0 deletions 3_middlewares/4_fastify/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Build stage
FROM node:18-alpine AS build

# Set working directory
WORKDIR /app

# Copy package files and install dependencies
COPY package*.json ./
RUN npm install --production

# Copy the application source code
COPY . .

# Production stage
FROM node:18-alpine

WORKDIR /app

# Copy only the necessary files from the build stage
COPY --from=build /app ./

# Expose the port the app will run on
EXPOSE 3000

# Start the Fastify application
CMD ["npm", "run", "start"]
23 changes: 23 additions & 0 deletions 3_middlewares/4_fastify/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Getting Started with [Fastify-CLI](https://www.npmjs.com/package/fastify-cli)
This project was bootstrapped with Fastify-CLI.

## Available Scripts

In the project directory, you can run:

### `npm run dev`

To start the app in dev mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.

### `npm start`

For production mode

### `npm run test`

Run the test cases.

## Learn More

To learn Fastify, check out the [Fastify documentation](https://fastify.dev/docs/latest/).
25 changes: 25 additions & 0 deletions 3_middlewares/4_fastify/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "4_fastify",
"version": "1.0.0",
"description": "This project was bootstrapped with Fastify-CLI.",
"main": "app.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "node --test test/**/*.test.js",
"start": "fastify start -l info app.js",
"dev": "fastify start -w -l info -P app.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"fastify": "^5.0.0",
"fastify-plugin": "^5.0.0",
"@fastify/autoload": "^6.0.0",
"@fastify/sensible": "^6.0.0",
"fastify-cli": "^7.2.0"
},
"devDependencies": {}
}
16 changes: 16 additions & 0 deletions 3_middlewares/4_fastify/plugins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Plugins Folder

Plugins define behavior that is common to all the routes in your
application. Authentication, caching, templates, and all the other cross
cutting concerns should be handled by plugins placed in this folder.

Files in this folder are typically defined through the
[`fastify-plugin`](https://github.com/fastify/fastify-plugin) module,
making them non-encapsulated. They can define decorators and set hooks
that will then be used in the rest of your application.

Check out:

* [The hitchhiker's guide to plugins](https://fastify.dev/docs/latest/Guides/Plugins-Guide/)
* [Fastify decorators](https://fastify.dev/docs/latest/Reference/Decorators/).
* [Fastify lifecycle](https://fastify.dev/docs/latest/Reference/Lifecycle/).
14 changes: 14 additions & 0 deletions 3_middlewares/4_fastify/plugins/sensible.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict'

const fp = require('fastify-plugin')

/**
* This plugins adds some utilities to handle http errors
*
* @see https://github.com/fastify/fastify-sensible
*/
module.exports = fp(async function (fastify, opts) {
fastify.register(require('@fastify/sensible'), {
errorHandler: false
})
})
12 changes: 12 additions & 0 deletions 3_middlewares/4_fastify/plugins/support.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict'

const fp = require('fastify-plugin')

// the use of fastify-plugin is required to be able
// to export the decorators to the outer scope

module.exports = fp(async function (fastify, opts) {
fastify.decorate('someSupport', function () {
return 'hugs'
})
})
29 changes: 29 additions & 0 deletions 3_middlewares/4_fastify/routes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Routes Folder

Routes define the pathways within your application.
Fastify's structure supports the modular monolith approach, where your
application is organized into distinct, self-contained modules.
This facilitates easier scaling and future transition to a microservice architecture.
In the future you might want to independently deploy some of those.

In this folder you should define all the routes that define the endpoints
of your web application.
Each service is a [Fastify
plugin](https://fastify.dev/docs/latest/Reference/Plugins/), it is
encapsulated (it can have its own independent plugins) and it is
typically stored in a file; be careful to group your routes logically,
e.g. all `/users` routes in a `users.js` file. We have added
a `root.js` file for you with a '/' root added.

If a single file becomes too large, create a folder and add a `index.js` file there:
this file must be a Fastify plugin, and it will be loaded automatically
by the application. You can now add as many files as you want inside that folder.
In this way you can create complex routes within a single monolith,
and eventually extract them.

If you need to share functionality between routes, place that
functionality into the `plugins` folder, and share it via
[decorators](https://fastify.dev/docs/latest/Reference/Decorators/).

If you're a bit confused about using `async/await` to write routes, you would
better take a look at [Promise resolution](https://fastify.dev/docs/latest/Reference/Routes/#promise-resolution) for more details.
80 changes: 80 additions & 0 deletions 3_middlewares/4_fastify/routes/root.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict'

// In-memory storage for items
let items = [];

module.exports = async function (fastify, opts) {

/**
* Create a new item
* @example http POST :3000/items name="Item1" description="This is Item1"
*/
fastify.post('/items', async (request, reply) => {
const { name, description } = request.body;
const id = items.length + 1; // Simple ID generation
const newItem = { id, name, description };
items.push(newItem);
reply.code(201).send(newItem); // Return the created item with status code 201
});

/**
* Read all items
* @example http :3000/items
*/
fastify.get('/items', async (request, reply) => {
return items; // Return all items
});

/**
* Read a single item by ID
* @example http :3000/items/1
*/
fastify.get('/items/:id', async (request, reply) => {
const { id } = request.params;
const item = items.find(i => i.id === parseInt(id));

if (!item) {
return reply.code(404).send({ message: "Item not found" }); // 404 if item is not found
}

return item;
});

/**
* Update an existing item by ID
* @example http PUT :3000/items/1 name="Item1 - Updated"
*/
fastify.put('/items/:id', async (request, reply) => {
const { id } = request.params;
const { name, description } = request.body;

let item = items.find(i => i.id === parseInt(id));

if (!item) {
return reply.code(404).send({ message: "Item not found" }); // 404 if item is not found
}

// Update the item
item = { ...item, name, description };
items = items.map(i => (i.id === parseInt(id) ? item : i));

return item;
});

/**
* Delete an item by ID
* @example http DELETE :3000/items/1
*/
fastify.delete('/items/:id', async (request, reply) => {
const { id } = request.params;

const itemIndex = items.findIndex(i => i.id === parseInt(id));
if (itemIndex === -1) {
return reply.code(404).send({ message: "Item not found" }); // 404 if item is not found
}

items.splice(itemIndex, 1); // Remove the item from the array
return { message: "Item deleted successfully" }; // Return success message
});

};
35 changes: 35 additions & 0 deletions 3_middlewares/4_fastify/test/helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict'

// This file contains code that we reuse
// between our tests.

const { build: buildApplication } = require('fastify-cli/helper')
const path = require('node:path')
const AppPath = path.join(__dirname, '..', 'app.js')

// Fill in this config with all the configurations
// needed for testing the application
function config () {
return {}
}

// automatically build and tear down our instance
async function build (t) {
// you can set all the options supported by the fastify CLI command
const argv = [AppPath]

// fastify-plugin ensures that all decorators
// are exposed for testing purposes, this is
// different from the production setup
const app = await buildApplication(argv, config())

// close the app after we are done
t.after(() => app.close())

return app
}

module.exports = {
config,
build
}
28 changes: 28 additions & 0 deletions 3_middlewares/4_fastify/test/plugins/support.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict'

const { test } = require('node:test')
const assert = require('node:assert')

const Fastify = require('fastify')
const Support = require('../../plugins/support')

test('support works standalone', async (t) => {
const fastify = Fastify()
fastify.register(Support)

await fastify.ready()
assert.equal(fastify.someSupport(), 'hugs')
})

// You can also use plugin with opts in fastify v2
//
// test('support works standalone', (t) => {
// t.plan(2)
// const fastify = Fastify()
// fastify.register(Support)
//
// fastify.ready((err) => {
// t.error(err)
// assert.equal(fastify.someSupport(), 'hugs')
// })
// })
Loading

0 comments on commit 9a3c9ee

Please sign in to comment.