diff --git a/.gitignore b/.gitignore index d9b7899..ecc1eb5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,10 @@ node_modules/ dist/ # Environment variables -.env* \ No newline at end of file +.env* + +# database +db/ + +# system files +.DS_Store \ No newline at end of file diff --git a/mongo-docker-compose.yml b/mongo-docker-compose.yml new file mode 100644 index 0000000..5fe8ebb --- /dev/null +++ b/mongo-docker-compose.yml @@ -0,0 +1,15 @@ +# Run the following command to start mongodb container +# docker compose -f mongo-docker-compose.yml up -d +version: '3' + +services: + mongo: + image: mongo:latest + container_name: cellar-mongo-db + ports: + - '27017:27017' + environment: + - MONGO_INITDB_ROOT_USERNAME=admin + - MONGO_INITDB_ROOT_PASSWORD=admin + volumes: + - ./db:/data/db diff --git a/package-lock.json b/package-lock.json index 0813f96..d49e92a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "ISC", "dependencies": { "dotenv": "^16.4.7", - "express": "^4.21.2" + "express": "^4.21.2", + "mongodb": "^6.15.0" }, "devDependencies": { "@saithodev/semantic-release-gitea": "^2.1.0", @@ -106,6 +107,15 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz", + "integrity": "sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1376,6 +1386,21 @@ "@types/send": "*" } }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1642,6 +1667,15 @@ "node": ">=8" } }, + "node_modules/bson": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz", + "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -4000,6 +4034,12 @@ "node": ">= 0.6" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, "node_modules/meow": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", @@ -4145,6 +4185,62 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mongodb": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.15.0.tgz", + "integrity": "sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.3", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -7609,6 +7705,15 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -8478,6 +8583,15 @@ "node": ">=0.10.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/spawn-error-forwarder": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", @@ -8850,6 +8964,18 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.0.tgz", + "integrity": "sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/traverse": { "version": "0.6.8", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", @@ -9110,6 +9236,28 @@ "node": ">= 0.8" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 72796f7..9f268d3 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "description": "Cellar Social API", "dependencies": { "dotenv": "^16.4.7", - "express": "^4.21.2" + "express": "^4.21.2", + "mongodb": "^6.15.0" }, "devDependencies": { "@saithodev/semantic-release-gitea": "^2.1.0", diff --git a/src/index.ts b/src/index.ts index 38d156a..50566fc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,22 @@ import express, { Express, Request, Response } from 'express' import dotenv from 'dotenv' +import { connectToDatabase } from './services/database.service' +import { usersRouter } from './routes/users.router' dotenv.config() const app: Express = express() const port = process.env.PORT || 3000 -app.get('/', (req: Request, res: Response) => { - console.log(`Request to '/' route received.`) +connectToDatabase() + .then(() => { + app.use('/users', usersRouter) - res.send('Cellar Social API is alive and kicking!') -}) - -app.listen(port, () => { - console.log(`[server]: Server is running at http://localhost:${port}`) -}) + app.listen(port, () => { + console.log(`Server started at http://localhost:${port}`) + }) + }) + .catch((error: Error) => { + console.error('Database connection failed', error) + process.exit() + }) diff --git a/src/models/user.ts b/src/models/user.ts new file mode 100644 index 0000000..9792907 --- /dev/null +++ b/src/models/user.ts @@ -0,0 +1,8 @@ +import { ObjectId } from 'mongodb' + +export default class User { + constructor( + public name: string, + public id?: ObjectId + ) {} +} diff --git a/src/routes/users.router.ts b/src/routes/users.router.ts new file mode 100644 index 0000000..73c0b4a --- /dev/null +++ b/src/routes/users.router.ts @@ -0,0 +1,38 @@ +// External Dependencies +import express, { Request, Response } from 'express' +import { ObjectId } from 'mongodb' +import { collections } from '../services/database.service' +import User from '../models/user' + +// Global Config +export const usersRouter = express.Router() + +usersRouter.use(express.json()) + +// GET +usersRouter.get('/', async (_req: Request, res: Response) => { + try { + const users = await collections.users?.find({}).toArray() + + res.status(200).send(users) + } catch (error: any) { + res.status(500).send(error.message) + } +}) + +// POST +usersRouter.post('/', async (req: Request, res: Response) => { + try { + const newUser = req.body as User + const result = await collections.users?.insertOne(newUser) + + result + ? res + .status(201) + .send(`Successfully created a new user with id ${result.insertedId}`) + : res.status(500).send('Failed to create a new user.') + } catch (error: any) { + console.error(error) + res.status(400).send(error.message) + } +}) diff --git a/src/services/database.service.ts b/src/services/database.service.ts new file mode 100644 index 0000000..8df9ed8 --- /dev/null +++ b/src/services/database.service.ts @@ -0,0 +1,27 @@ +// External Dependencies +import * as mongoDB from 'mongodb' +import * as dotenv from 'dotenv' + +// Global Variables +export const collections: { users?: mongoDB.Collection } = {} + +// Initialize Connection +export async function connectToDatabase() { + dotenv.config() + + const client: mongoDB.MongoClient = new mongoDB.MongoClient( + process.env.DB_CONN_STRING as any + ) + + await client.connect() + + const db: mongoDB.Db = client.db(process.env.DB_NAME) + + const usersCollection: mongoDB.Collection = db.collection('users') + + collections.users = usersCollection + + console.log( + `Successfully connected to database: ${db.databaseName} and collection: ${usersCollection.collectionName}` + ) +}