├── .DS_Store ├── config └── db.config.js ├── README.md ├── models ├── tag.model.js ├── user.model.js ├── posttag.model.js ├── profile.model.js ├── post.model.js └── index.js ├── routes ├── tag.routes.js ├── user.routes.js └── post.routes.js ├── controllers ├── tag.controller.js ├── user.controller.js └── post.controller.js ├── server.js ├── package.json └── LICENSE /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didinj/node-express-sequelize-postgresql-association/HEAD/.DS_Store -------------------------------------------------------------------------------- /config/db.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | HOST: "localhost", 3 | USER: "djamware", 4 | PASSWORD: "dj@mw@r3", 5 | DB: "blog_db", 6 | DIALECT: "postgres" 7 | }; 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node, Express, Sequelize, and PostgreSQL Association Example 2 | 3 | Read the full tutorial [here](https://www.djamware.com/post/5bb1f05280aca74669894417/node-express-sequelize-and-postgresql-association-example). 4 | -------------------------------------------------------------------------------- /models/tag.model.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | return sequelize.define("Tag", { 3 | id: { 4 | type: DataTypes.INTEGER, 5 | primaryKey: true, 6 | autoIncrement: true 7 | }, 8 | name: DataTypes.STRING 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /routes/tag.routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const tagController = require("../controllers/tag.controller"); 4 | 5 | router.post("/", tagController.createTag); 6 | router.get("/", tagController.getAllTags); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /routes/user.routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const userController = require("../controllers/user.controller"); 4 | 5 | router.post("/", userController.createUser); 6 | router.get("/", userController.getAllUsers); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /models/user.model.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | return sequelize.define("User", { 3 | id: { 4 | type: DataTypes.INTEGER, 5 | primaryKey: true, 6 | autoIncrement: true 7 | }, 8 | name: DataTypes.STRING, 9 | email: DataTypes.STRING 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /routes/post.routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const postController = require("../controllers/post.controller"); 4 | 5 | router.post("/", postController.createPost); 6 | router.get("/", postController.getAllPosts); 7 | router.post("/add-tag", postController.addTagToPost); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /models/posttag.model.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | return sequelize.define("PostTag", { 3 | postId: { 4 | type: DataTypes.INTEGER, 5 | references: { model: "Posts", key: "id" } 6 | }, 7 | tagId: { 8 | type: DataTypes.INTEGER, 9 | references: { model: "Tags", key: "id" } 10 | } 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /models/profile.model.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | return sequelize.define("Profile", { 3 | id: { 4 | type: DataTypes.INTEGER, 5 | primaryKey: true, 6 | autoIncrement: true 7 | }, 8 | bio: DataTypes.TEXT, 9 | userId: { 10 | type: DataTypes.INTEGER, 11 | allowNull: false 12 | } 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /models/post.model.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | return sequelize.define("Post", { 3 | id: { 4 | type: DataTypes.INTEGER, 5 | primaryKey: true, 6 | autoIncrement: true 7 | }, 8 | title: DataTypes.STRING, 9 | content: DataTypes.TEXT, 10 | userId: { 11 | type: DataTypes.INTEGER, 12 | allowNull: false 13 | } 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /controllers/tag.controller.js: -------------------------------------------------------------------------------- 1 | const db = require("../models"); 2 | const { Tag } = db; 3 | 4 | exports.createTag = async (req, res) => { 5 | try { 6 | const tag = await Tag.create(req.body); 7 | res.json(tag); 8 | } catch (err) { 9 | res.status(500).json({ error: err.message }); 10 | } 11 | }; 12 | 13 | exports.getAllTags = async (req, res) => { 14 | try { 15 | const tags = await Tag.findAll(); 16 | res.json(tags); 17 | } catch (err) { 18 | res.status(500).json({ error: err.message }); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const app = express(); 3 | const db = require("./models"); 4 | 5 | app.use(express.json()); 6 | 7 | // Sync DB 8 | db.sequelize.sync({ force: true }).then(() => { 9 | console.log("Database synced"); 10 | }); 11 | 12 | app.use("/api/users", require("./routes/user.routes")); 13 | app.use("/api/posts", require("./routes/post.routes")); 14 | app.use("/api/tags", require("./routes/tag.routes")); 15 | 16 | const PORT = process.env.PORT || 3000; 17 | app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-sequelize-associations", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "nodemon server.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "type": "commonjs", 14 | "dependencies": { 15 | "express": "^5.1.0", 16 | "pg": "^8.16.3", 17 | "pg-hstore": "^2.3.4", 18 | "sequelize": "^6.37.7" 19 | }, 20 | "devDependencies": { 21 | "nodemon": "^3.1.10" 22 | } 23 | } -------------------------------------------------------------------------------- /controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | const db = require("../models"); 2 | const { User, Profile } = db; 3 | 4 | exports.createUser = async (req, res) => { 5 | try { 6 | const user = await User.create(req.body, { include: ["profile"] }); 7 | res.json(user); 8 | } catch (err) { 9 | res.status(500).json({ error: err.message }); 10 | } 11 | }; 12 | 13 | exports.getAllUsers = async (req, res) => { 14 | try { 15 | const users = await User.findAll({ include: ["profile", "posts"] }); 16 | res.json(users); 17 | } catch (err) { 18 | res.status(500).json({ error: err.message }); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /controllers/post.controller.js: -------------------------------------------------------------------------------- 1 | const db = require("../models"); 2 | const { Post, User, Tag } = db; 3 | 4 | exports.createPost = async (req, res) => { 5 | try { 6 | const post = await Post.create(req.body); 7 | res.json(post); 8 | } catch (err) { 9 | res.status(500).json({ error: err.message }); 10 | } 11 | }; 12 | 13 | exports.getAllPosts = async (req, res) => { 14 | try { 15 | const posts = await Post.findAll({ 16 | include: ["user", "tags"] 17 | }); 18 | res.json(posts); 19 | } catch (err) { 20 | res.status(500).json({ error: err.message }); 21 | } 22 | }; 23 | 24 | exports.addTagToPost = async (req, res) => { 25 | try { 26 | const { postId, tagId } = req.body; 27 | const post = await Post.findByPk(postId); 28 | const tag = await Tag.findByPk(tagId); 29 | if (post && tag) { 30 | await post.addTag(tag); 31 | res.json({ message: "Tag added to post." }); 32 | } else { 33 | res.status(404).json({ message: "Post or Tag not found." }); 34 | } 35 | } catch (err) { 36 | res.status(500).json({ error: err.message }); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Didin Jamaludin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | const { Sequelize, DataTypes } = require("sequelize"); 2 | const dbConfig = require("../config/db.config"); 3 | 4 | const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, { 5 | host: dbConfig.HOST, 6 | dialect: dbConfig.DIALECT, 7 | logging: false 8 | }); 9 | 10 | // Initialize models 11 | const db = {}; 12 | db.Sequelize = Sequelize; 13 | db.sequelize = sequelize; 14 | 15 | db.User = require("./user.model")(sequelize, DataTypes); 16 | db.Profile = require("./profile.model")(sequelize, DataTypes); 17 | db.Post = require("./post.model")(sequelize, DataTypes); 18 | db.Tag = require("./tag.model")(sequelize, DataTypes); 19 | db.PostTag = require("./posttag.model")(sequelize, DataTypes); 20 | 21 | // Associations 22 | 23 | // One-to-One: User ↔ Profile 24 | db.User.hasOne(db.Profile, { foreignKey: "userId", as: "profile" }); 25 | db.Profile.belongsTo(db.User, { foreignKey: "userId", as: "user" }); 26 | 27 | // One-to-Many: User → Posts 28 | db.User.hasMany(db.Post, { foreignKey: "userId", as: "posts" }); 29 | db.Post.belongsTo(db.User, { foreignKey: "userId", as: "user" }); 30 | 31 | // Many-to-Many: Post ↔ Tag (via PostTag) 32 | db.Post.belongsToMany(db.Tag, { 33 | through: db.PostTag, 34 | as: "tags", 35 | foreignKey: "postId" 36 | }); 37 | db.Tag.belongsToMany(db.Post, { 38 | through: db.PostTag, 39 | as: "posts", 40 | foreignKey: "tagId" 41 | }); 42 | 43 | module.exports = db; 44 | --------------------------------------------------------------------------------