├── .gitignore ├── .babelrc ├── .env ├── .prettierrc ├── .travis.yml ├── src ├── routes │ ├── index.js │ ├── session.js │ ├── user.js │ └── message.js ├── models │ ├── message.js │ ├── index.js │ └── user.js └── index.js ├── .github └── FUNDING.yml ├── package.json ├── docker-compose.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | logfile 2 | 3 | node_modules/ -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | 3 | DATABASE=mydatabase 4 | DATABASE_USER=postgres 5 | DATABASE_PASSWORD=postgres -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 70, 6 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - stable 5 | 6 | install: 7 | - npm install 8 | 9 | script: 10 | - npm test 11 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import session from './session'; 2 | import user from './user'; 3 | import message from './message'; 4 | 5 | export default { 6 | session, 7 | user, 8 | message, 9 | }; 10 | -------------------------------------------------------------------------------- /src/routes/session.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | const router = Router(); 4 | 5 | router.get('/', async (req, res) => { 6 | const user = await req.context.models.User.findByPk( 7 | req.context.me.id, 8 | ); 9 | return res.send(user); 10 | }); 11 | 12 | export default router; 13 | -------------------------------------------------------------------------------- /src/routes/user.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | const router = Router(); 4 | 5 | router.get('/', async (req, res) => { 6 | const users = await req.context.models.User.findAll(); 7 | return res.send(users); 8 | }); 9 | 10 | router.get('/:userId', async (req, res) => { 11 | const user = await req.context.models.User.findByPk( 12 | req.params.userId, 13 | ); 14 | return res.send(user); 15 | }); 16 | 17 | export default router; 18 | -------------------------------------------------------------------------------- /src/models/message.js: -------------------------------------------------------------------------------- 1 | const getMessageModel = (sequelize, { DataTypes }) => { 2 | const Message = sequelize.define('message', { 3 | text: { 4 | type: DataTypes.STRING, 5 | allowNull: false, 6 | validate: { 7 | notEmpty: true, 8 | }, 9 | }, 10 | }); 11 | 12 | Message.associate = (models) => { 13 | Message.belongsTo(models.User); 14 | }; 15 | 16 | return Message; 17 | }; 18 | 19 | export default getMessageModel; 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: rwieruch 4 | patreon: # rwieruch 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with a single custom sponsorship URL 13 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | import Sequelize from 'sequelize'; 2 | 3 | import getUserModel from './user'; 4 | import getMessageModel from './message'; 5 | 6 | const sequelize = new Sequelize( 7 | process.env.DATABASE, 8 | process.env.DATABASE_USER, 9 | process.env.DATABASE_PASSWORD, 10 | { 11 | dialect: 'postgres', 12 | }, 13 | ); 14 | 15 | const models = { 16 | User: getUserModel(sequelize, Sequelize), 17 | Message: getMessageModel(sequelize, Sequelize), 18 | }; 19 | 20 | Object.keys(models).forEach((key) => { 21 | if ('associate' in models[key]) { 22 | models[key].associate(models); 23 | } 24 | }); 25 | 26 | export { sequelize }; 27 | 28 | export default models; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-express-postgresql-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon --exec babel-node src/index.js", 8 | "test": "echo \"No test specified\" && exit 0" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@babel/core": "^7.17.5", 15 | "@babel/node": "^7.16.8", 16 | "@babel/preset-env": "^7.16.11", 17 | "nodemon": "^2.0.15" 18 | }, 19 | "dependencies": { 20 | "cors": "^2.8.5", 21 | "dotenv": "^16.0.0", 22 | "express": "^4.17.3", 23 | "pg": "^8.7.3", 24 | "sequelize": "^6.16.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/models/user.js: -------------------------------------------------------------------------------- 1 | const getUserModel = (sequelize, { DataTypes }) => { 2 | const User = sequelize.define('user', { 3 | username: { 4 | type: DataTypes.STRING, 5 | unique: true, 6 | allowNull: false, 7 | validate: { 8 | notEmpty: true, 9 | }, 10 | }, 11 | }); 12 | 13 | User.associate = (models) => { 14 | User.hasMany(models.Message, { onDelete: 'CASCADE' }); 15 | }; 16 | 17 | User.findByLogin = async (login) => { 18 | let user = await User.findOne({ 19 | where: { username: login }, 20 | }); 21 | 22 | if (!user) { 23 | user = await User.findOne({ 24 | where: { email: login }, 25 | }); 26 | } 27 | 28 | return user; 29 | }; 30 | 31 | return User; 32 | }; 33 | 34 | export default getUserModel; 35 | -------------------------------------------------------------------------------- /src/routes/message.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | const router = Router(); 4 | 5 | router.get('/', async (req, res) => { 6 | const messages = await req.context.models.Message.findAll(); 7 | return res.send(messages); 8 | }); 9 | 10 | router.get('/:messageId', async (req, res) => { 11 | const message = await req.context.models.Message.findByPk( 12 | req.params.messageId, 13 | ); 14 | return res.send(message); 15 | }); 16 | 17 | router.post('/', async (req, res) => { 18 | const message = await req.context.models.Message.create({ 19 | text: req.body.text, 20 | userId: req.context.me.id, 21 | }); 22 | 23 | return res.send(message); 24 | }); 25 | 26 | router.delete('/:messageId', async (req, res) => { 27 | const result = await req.context.models.Message.destroy({ 28 | where: { id: req.params.messageId }, 29 | }); 30 | 31 | return res.send(true); 32 | }); 33 | 34 | export default router; 35 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | postgres: 5 | image: postgres:14.2-alpine 6 | environment: 7 | POSTGRES_USER: ${POSTGRES_USER:-postgres} 8 | POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} 9 | POSTGRES_DB: mydatabase 10 | PGDATA: /data/mydatabase 11 | volumes: 12 | - pgstore:/var/lib/postgresql/data 13 | ports: 14 | - '5432:5432' 15 | networks: 16 | - postgres 17 | restart: unless-stopped 18 | 19 | pgadmin: 20 | image: dpage/pgadmin4:6.5 21 | environment: 22 | PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-admin@admin.com} 23 | PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} 24 | volumes: 25 | - pgadmin:/root/.pgadmin 26 | ports: 27 | - '5050:80' 28 | networks: 29 | - postgres 30 | depends_on: 31 | - postgres 32 | restart: unless-stopped 33 | 34 | networks: 35 | postgres: 36 | driver: bridge 37 | 38 | volumes: 39 | pgstore: 40 | pgadmin: 41 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import cors from 'cors'; 3 | import express from 'express'; 4 | 5 | import models, { sequelize } from './models'; 6 | import routes from './routes'; 7 | 8 | const app = express(); 9 | 10 | // * Application-Level Middleware * // 11 | 12 | // Third-Party Middleware 13 | 14 | app.use(cors()); 15 | 16 | // Built-In Middleware 17 | 18 | app.use(express.json()); 19 | app.use(express.urlencoded({ extended: true })); 20 | 21 | // Custom Middleware 22 | 23 | app.use(async (req, res, next) => { 24 | req.context = { 25 | models, 26 | me: await models.User.findByLogin('rwieruch'), 27 | }; 28 | next(); 29 | }); 30 | 31 | // * Routes * // 32 | 33 | app.use('/session', routes.session); 34 | app.use('/users', routes.user); 35 | app.use('/messages', routes.message); 36 | 37 | // * Start * // 38 | 39 | const eraseDatabaseOnSync = true; 40 | 41 | sequelize.sync({ force: eraseDatabaseOnSync }).then(async () => { 42 | if (eraseDatabaseOnSync) { 43 | createUsersWithMessages(); 44 | } 45 | 46 | app.listen(process.env.PORT, () => 47 | console.log(`Example app listening on port ${process.env.PORT}!`), 48 | ); 49 | }); 50 | 51 | const createUsersWithMessages = async () => { 52 | await models.User.create( 53 | { 54 | username: 'rwieruch', 55 | messages: [ 56 | { 57 | text: 'Published the Road to learn React', 58 | }, 59 | ], 60 | }, 61 | { 62 | include: [models.Message], 63 | }, 64 | ); 65 | 66 | await models.User.create( 67 | { 68 | username: 'ddavids', 69 | messages: [ 70 | { 71 | text: 'Happy to release ...', 72 | }, 73 | { 74 | text: 'Published a complete ...', 75 | }, 76 | ], 77 | }, 78 | { 79 | include: [models.Message], 80 | }, 81 | ); 82 | }; 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Node with Express + PostgreSQL Server 2 | 3 | [![Build Status](https://travis-ci.org/rwieruch/node-express-postgresql-server.svg?branch=master)](https://travis-ci.org/rwieruch/node-express-postgresql-server) [![Slack](https://slack-the-road-to-learn-react.wieruch.com/badge.svg)](https://slack-the-road-to-learn-react.wieruch.com/) [![Greenkeeper badge](https://badges.greenkeeper.io/rwieruch/node-express-postgresql-server.svg)](https://greenkeeper.io/) 4 | 5 | An easy way to get started with a Express server with PostgreSQL with Node.js. [Read more about it.](https://www.robinwieruch.de/postgres-express-setup-tutorial/) 6 | 7 | ## Features 8 | 9 | - Express 10 | - REST API 11 | - PostgreSQL 12 | 13 | ## Requirements 14 | 15 | - [node & npm](https://nodejs.org/en/) 16 | - [git](https://www.robinwieruch.de/git-essential-commands/) 17 | 18 | ## Installation 19 | 20 | - `git clone git@github.com:rwieruch/node-express-postgresql-server.git` 21 | - `cd node-express-postgresql-server` 22 | - `npm install` 23 | - `docker-compose up` 24 | - `npm start` 25 | - optional: include _.env_ in your _.gitignore_ 26 | 27 | ### GET Routes 28 | 29 | - visit http://localhost:3000 30 | - /messages 31 | - /messages/1 32 | - /users 33 | - /users/1 34 | 35 | ### Beyond GET Routes 36 | 37 | #### CURL 38 | 39 | - Create a message with: 40 | - `curl -X POST -H "Content-Type:application/json" http://localhost:3000/messages -d '{"text":"Hi again, World"}'` 41 | - Delete a message with: 42 | - `curl -X DELETE -H "Content-Type:application/json" http://localhost:3000/messages/1` 43 | 44 | #### Postman 45 | 46 | - Install [Postman](https://www.getpostman.com/apps) to interact with REST API 47 | - Create a message with: 48 | - URL: http://localhost:3000/messages 49 | - Method: POST 50 | - Body: raw + JSON (application/json) 51 | - Body Content: `{ "text": "Hi again, World" }` 52 | - Delete a message with: 53 | - URL: http://localhost:3000/messages/1 54 | - Method: DELETE 55 | --------------------------------------------------------------------------------