├── .env ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── package.json ├── public ├── css │ ├── styles.css │ ├── styles.css.map │ └── styles.scss └── img │ └── default.jpg └── src ├── routes └── news.js └── views ├── news.ejs ├── newsSearch.ejs ├── newsSingle.ejs └── partials └── search.ejs /.env: -------------------------------------------------------------------------------- 1 | API_KEY = 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://www.buymeacoffee.com/RaddyTheBrand'] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | package-lock.json 4 | test/*.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Raddy 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build News Website With Node.js, Express & EJS - WP Rest API 2 | 3 | This was created as part of YouTube Video. Links below 4 | 5 |  6 | 7 | ## Installation 8 | 9 | To run this project, install it locally using npm: 10 | 11 | ``` 12 | $ npm install 13 | $ npm start 14 | ``` 15 | 16 | ### YouTube Video & Article 17 | 18 | [Node.js News Website](https://youtu.be/EkQc-8uzxIA) 19 | 20 | [Read Article](https://raddy.dev/blog/build-news-website-with-node-js-express-ejs-wp-rest-api/) 21 | 22 | ### YouTube Channel - RaddyTheBrand 23 | 24 | [Subscribe to my YouTube Channel](https://www.youtube.com/channel/UCvXscyQ0cLzPZeNOeXI45Sw?sub_confirmation=1) 25 | 26 | ### Website 27 | 28 | [www.raddy.dev](https://www.raddy.dev) 29 | 30 | ### Donations 31 | 32 | [Via Paypal](https://www.paypal.me/RadoslavAngelov) 33 | 34 | [Buy me a Coffee](https://www.buymeacoffee.com/RaddyTheBrand) 35 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const bodyParser = require('body-parser') 3 | 4 | const app = express() 5 | const port = 5000 6 | 7 | // Static Files 8 | app.use(express.static('public')) 9 | app.use('/css', express.static(__dirname + 'public/css')) 10 | app.use('/img', express.static(__dirname + 'public/img')) 11 | app.use('/js', express.static(__dirname + 'public/js')) 12 | 13 | // Templating Engine 14 | app.set('views', './src/views') 15 | app.set('view engine', 'ejs') 16 | 17 | app.use(bodyParser.urlencoded({ extended: true })) 18 | 19 | // Routes 20 | const newsRouter = require('./src/routes/news') 21 | 22 | app.use('/', newsRouter) 23 | app.use('/article', newsRouter) 24 | 25 | // Listen on port 5000 26 | app.listen(process.env.PORT || port, () => console.log(`Listening on port ${port}`)) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-news", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node app.js", 8 | "dev": "nodemon app.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "axios": ">=0.21.1", 15 | "body-parser": "^1.19.0", 16 | "ejs": "^3.1.5", 17 | "express": "^4.17.1" 18 | }, 19 | "devDependencies": { 20 | "nodemon": "^2.0.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: 'Source Sans Pro', sans-serif; 4 | background-color: #f6f6f6; 5 | } 6 | 7 | img { 8 | max-width: 100%; 9 | } 10 | 11 | h2 { 12 | font-size: 1.6rem; 13 | } 14 | 15 | .header { 16 | display: -webkit-box; 17 | display: -ms-flexbox; 18 | display: flex; 19 | -webkit-box-pack: justify; 20 | -ms-flex-pack: justify; 21 | justify-content: space-between; 22 | -webkit-box-align: center; 23 | -ms-flex-align: center; 24 | align-items: center; 25 | padding: 10px; 26 | color: #fff; 27 | background-color: #10555A; 28 | margin-bottom: 10px; 29 | } 30 | 31 | .header__search input[type=text] { 32 | padding: 6px; 33 | border: none; 34 | } 35 | 36 | .header__search input[type=submit] { 37 | float: right; 38 | padding: 6px 10px; 39 | border: none; 40 | cursor: pointer; 41 | } 42 | 43 | .wrapper { 44 | padding: 0 1rem; 45 | } 46 | 47 | .news { 48 | display: -ms-grid; 49 | display: grid; 50 | -ms-grid-columns: (minmax(360px, 1fr))[auto-fill]; 51 | grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); 52 | grid-gap: 2rem; 53 | } 54 | 55 | .news__card { 56 | text-decoration: none; 57 | color: #080808; 58 | background-color: #fff; 59 | padding: 20px; 60 | } 61 | 62 | .news__card:hover { 63 | -webkit-box-shadow: 0 3px 3px rgba(0, 0, 0, 0.16), 0 3px 3px rgba(0, 0, 0, 0.23); 64 | box-shadow: 0 3px 3px rgba(0, 0, 0, 0.16), 0 3px 3px rgba(0, 0, 0, 0.23); 65 | } 66 | 67 | .news-single { 68 | background-color: #fff; 69 | max-width: 1300px; 70 | margin: 0 auto; 71 | padding: 2rem; 72 | } 73 | /*# sourceMappingURL=styles.css.map */ -------------------------------------------------------------------------------- /public/css/styles.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "AAAA,AAAA,IAAI,CAAC;EACD,MAAM,EAAE,CAAC;EACT,WAAW,EAAE,6BAA6B;EAC1C,gBAAgB,EAAE,OAAO;CAC5B;;AAED,AAAA,GAAG,CAAC;EAAE,SAAS,EAAE,IAAI;CAAI;;AACzB,AAAA,EAAE,CAAC;EAAE,SAAS,EAAE,MAAM;CAAI;;AAE1B,AAAA,OAAO,CAAC;EACJ,OAAO,EAAE,IAAI;EACb,eAAe,EAAE,aAAa;EAC9B,WAAW,EAAE,MAAM;EACnB,OAAO,EAAE,IAAI;EACb,KAAK,EAAE,IAAI;EACX,gBAAgB,EAAE,OAAO;EACzB,aAAa,EAAE,IAAI;CAetB;;AAbI,AACG,eADK,CACL,KAAK,CAAA,AAAA,IAAC,CAAD,IAAC,AAAA,EAAW;EACb,OAAO,EAAE,GAAG;EACZ,MAAM,EAAE,IAAI;CACf;;AAJJ,AAMG,eANK,CAML,KAAK,CAAA,AAAA,IAAC,CAAD,MAAC,AAAA,EAAa;EACf,KAAK,EAAE,KAAK;EACZ,OAAO,EAAE,QAAQ;EACjB,MAAM,EAAE,IAAI;EACZ,MAAM,EAAE,OAAO;CAClB;;AAIT,AAAA,QAAQ,CAAC;EAAE,OAAO,EAAE,MAAO;CAAE;;AAE7B,AAAA,KAAK,CAAC;EACF,OAAO,EAAE,IAAI;EACb,qBAAqB,EAAE,qCAAqC;EAC5D,QAAQ,EAAE,IAAI;CAajB;;AAXI,AAAD,WAAO,CAAC;EACJ,eAAe,EAAE,IAAI;EACrB,KAAK,EAAE,OAAY;EACnB,gBAAgB,EAAE,IAAI;EACtB,OAAO,EAAE,IAAI;CAMhB;;AAVA,AAMG,WANG,AAMF,MAAM,CAAC;EACJ,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,mBAAgB,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,mBAAgB;CACrE;;AAKT,AAAA,YAAY,CAAC;EACT,gBAAgB,EAAE,IAAI;EACtB,SAAS,EAAE,MAAM;EACjB,MAAM,EAAE,MAAM;EACd,OAAO,EAAE,IAAI;CAChB", 4 | "sources": [ 5 | "styles.scss" 6 | ], 7 | "names": [], 8 | "file": "styles.css" 9 | } -------------------------------------------------------------------------------- /public/css/styles.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: 'Source Sans Pro', sans-serif; 4 | background-color: #f6f6f6; 5 | } 6 | 7 | img { max-width: 100%; } 8 | h2 { font-size: 1.6rem; } 9 | 10 | .header { 11 | display: flex; 12 | justify-content: space-between; 13 | align-items: center; 14 | padding: 10px; 15 | color: #fff; 16 | background-color: #10555A; 17 | margin-bottom: 10px; 18 | 19 | &__search { 20 | input[type=text] { 21 | padding: 6px; 22 | border: none; 23 | } 24 | 25 | input[type=submit] { 26 | float: right; 27 | padding: 6px 10px; 28 | border: none; 29 | cursor: pointer; 30 | } 31 | } 32 | } 33 | 34 | .wrapper { padding: 0 1rem } 35 | 36 | .news { 37 | display: grid; 38 | grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); 39 | grid-gap: 2rem; 40 | 41 | &__card { 42 | text-decoration: none; 43 | color: rgb(8, 8, 8); 44 | background-color: #fff; 45 | padding: 20px; 46 | 47 | &:hover { 48 | box-shadow: 0 3px 3px rgba(0,0,0,0.16), 0 3px 3px rgba(0,0,0,0.23); 49 | } 50 | 51 | } 52 | } 53 | 54 | .news-single { 55 | background-color: #fff; 56 | max-width: 1300px; 57 | margin: 0 auto; 58 | padding: 2rem; 59 | } -------------------------------------------------------------------------------- /public/img/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaddyTheBrand/Node.js-News-Website/dbbe8fc25deb180b162aef4abb6491e49593fb9d/public/img/default.jpg -------------------------------------------------------------------------------- /src/routes/news.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const newsRouter = express.Router() 3 | const axios = require('axios') 4 | 5 | newsRouter.get('', async (req, res) => { 6 | try { 7 | const newsAPI = await axios.get(`https://raddy.dev/wp-json/wp/v2/posts/`) 8 | res.render('news', { articles: newsAPI.data }) 9 | } catch (err) { 10 | if (err.response) { 11 | res.render('news', { articles: null }) 12 | console.log(err.response.data) 13 | console.log(err.response.status) 14 | console.log(err.response.headers) 15 | } else if (err.request) { 16 | res.render('news', { articles: null }) 17 | console.log(err.requiest) 18 | } else { 19 | res.render('news', { articles: null }) 20 | console.error('Error', err.message) 21 | } 22 | } 23 | }) 24 | 25 | newsRouter.get('/:id', async (req, res) => { 26 | let articleID = req.params.id 27 | 28 | try { 29 | const newsAPI = await axios.get(`https://raddy.dev/wp-json/wp/v2/posts/${articleID}`) 30 | res.render('newsSingle', { article: newsAPI.data }) 31 | } catch (err) { 32 | if (err.response) { 33 | res.render('newsSingle', { article: null }) 34 | console.log(err.response.data) 35 | console.log(err.response.status) 36 | console.log(err.response.headers) 37 | } else if (err.requiest) { 38 | res.render('newsSingle', { article: null }) 39 | console.log(err.requiest) 40 | } else { 41 | res.render('newsSingle', { article: null }) 42 | console.error('Error', err.message) 43 | } 44 | } 45 | }) 46 | 47 | 48 | newsRouter.post('', async (req, res) => { 49 | let search = req.body.search 50 | try { 51 | const newsAPI = await axios.get(`https://raddy.dev/wp-json/wp/v2/posts?search=${search}`) 52 | res.render('newsSearch', { articles: newsAPI.data }) 53 | } catch (err) { 54 | if (err.response) { 55 | res.render('newsSearch', { articles: null }) 56 | console.log(err.response.data) 57 | console.log(err.response.status) 58 | console.log(err.response.headers) 59 | } else if (err.requiest) { 60 | res.render('newsSearch', { articles: null }) 61 | console.log(err.requiest) 62 | } else { 63 | res.render('newsSearch', { articles: null }) 64 | console.error('Error', err.message) 65 | } 66 | } 67 | }) 68 | 69 | 70 | module.exports = newsRouter 71 | -------------------------------------------------------------------------------- /src/views/news.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |<%- article.excerpt.rendered %>
24 | 25 | <% }) %> 26 | <% } else { %> 27 | No posts found. 28 | <% } %> 29 |<%- article.excerpt.rendered %>
24 | 25 | <% }) %> 26 | <% } else { %> 27 | No posts found. 28 | <% } %> 29 |<%- article.content.rendered %>
22 | <% } else { %> 23 | No posts found. 24 | <% } %> 25 |