├── .gitignore ├── README.md ├── backup-database.js ├── index.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | *.iml 4 | *.env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automated Backups with Postgres and NodeJS 2 | 3 | This project is an overview of how to use `pg_dump` and `cron` to 4 | automate backing up a Postgres database and sending it to a remote location. 5 | 6 | It is composed of two files - `backup-database.js` and `index.js`. All relevant 7 | methods are contained in the former file, which only exposes a single method. 8 | 9 | In order to get the project to work, create a `.env` file and enter the following details 10 | 11 | ```.env 12 | DB_NAME= 13 | DB_USER= 14 | PGPASS= 15 | ``` -------------------------------------------------------------------------------- /backup-database.js: -------------------------------------------------------------------------------- 1 | const { execute } = require('@getvim/execute'); 2 | const compress = require('gzipme'); 3 | const axios = require('axios'); 4 | const FormData = require('form-data'); 5 | const fs = require('fs'); 6 | const cron = require('node-cron'); 7 | 8 | const dotenv = require('dotenv'); 9 | dotenv.config(); 10 | 11 | const username = process.env.DB_USER; 12 | const database = process.env.DB_NAME; 13 | const date = new Date(); 14 | 15 | const currentDate = `${date.getFullYear()}.${date.getMonth() + 1}.${date.getDate()}.${date.getHours()}.${date.getMinutes()}`; 16 | // this can be a path if you don't want it saved in your current folder. 17 | // eg. const path = require('path') 18 | //const file = path.join('./src', fileName) 19 | const fileName = `database-backup-${currentDate}.tar`; 20 | const fileNameGzip = `${fileName}.tar.gz`; 21 | 22 | function backup() { 23 | execute( 24 | `pg_dump -U ${username} -d ${database} -f ${fileName} -F t`, 25 | ).then(async ()=> { 26 | await compress(fileName); 27 | fs.unlinkSync(fileName); 28 | console.log("Finito"); 29 | }).catch(err=> { 30 | console.log(err); 31 | }) 32 | } 33 | 34 | function restore() { 35 | execute(`pg_restore -cC -d ${database} ${fileNameGzip}`) 36 | .then(async ()=> { 37 | console.log("Restored"); 38 | }).catch(err=> { 39 | console.log(err); 40 | }) 41 | } 42 | 43 | function sendToBackupServer(fileName = fileNameGzip) { 44 | const form = new FormData(); 45 | form.append('file', fileName); 46 | axios.post('http://my.backupserver.org/private', form, { 47 | headers: form.getHeaders(), 48 | }).then(result => { 49 | // Handle result… 50 | console.log(result.data); 51 | fs.unlinkSync(fileNameGzip); 52 | }).catch(err => { 53 | // log error, send it to sentry... etc 54 | console.error(err); 55 | }); 56 | } 57 | 58 | function startSchedule() { 59 | cron.schedule('0 */2 * * *', () => { 60 | backup() 61 | sendToBackupServer(); 62 | }, {}); 63 | } 64 | 65 | module.exports = { 66 | startSchedule 67 | } 68 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const {startSchedule} = require('./backup-database'); 2 | 3 | startSchedule(); -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "automated-nodejs-backups", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@getvim/execute": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/@getvim/execute/-/execute-1.0.0.tgz", 10 | "integrity": "sha512-DBtKHtKq4GKNnHL8pmiZb2y7uhkfVaQljqlmOJODDtJJfVOPQtUMMJCbEBTbvrqTnOlzgcgftUIsJCbLNgukdg==" 11 | }, 12 | "asynckit": { 13 | "version": "0.4.0", 14 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 15 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 16 | }, 17 | "axios": { 18 | "version": "0.21.0", 19 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", 20 | "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", 21 | "requires": { 22 | "follow-redirects": "^1.10.0" 23 | } 24 | }, 25 | "combined-stream": { 26 | "version": "1.0.8", 27 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 28 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 29 | "requires": { 30 | "delayed-stream": "~1.0.0" 31 | } 32 | }, 33 | "delayed-stream": { 34 | "version": "1.0.0", 35 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 36 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 37 | }, 38 | "dotenv": { 39 | "version": "8.2.0", 40 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 41 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 42 | }, 43 | "follow-redirects": { 44 | "version": "1.13.0", 45 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", 46 | "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" 47 | }, 48 | "form-data": { 49 | "version": "3.0.0", 50 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", 51 | "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", 52 | "requires": { 53 | "asynckit": "^0.4.0", 54 | "combined-stream": "^1.0.8", 55 | "mime-types": "^2.1.12" 56 | } 57 | }, 58 | "gzipme": { 59 | "version": "1.0.0", 60 | "resolved": "https://registry.npmjs.org/gzipme/-/gzipme-1.0.0.tgz", 61 | "integrity": "sha512-wxwgzH0vbi7RpWPYPRq+3laKr7Di4S4nYnFCQbxFpkcuxO49LVC7NUS7ZDnPLVRZcEDL4iZFCqnLCFSvoBIIhw==", 62 | "requires": { 63 | "commander": "6.1.0" 64 | }, 65 | "dependencies": { 66 | "commander": { 67 | "version": "6.1.0", 68 | "resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz", 69 | "integrity": "sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==" 70 | } 71 | } 72 | }, 73 | "mime-db": { 74 | "version": "1.44.0", 75 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 76 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 77 | }, 78 | "mime-types": { 79 | "version": "2.1.27", 80 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 81 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 82 | "requires": { 83 | "mime-db": "1.44.0" 84 | } 85 | }, 86 | "node-cron": { 87 | "version": "2.0.3", 88 | "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-2.0.3.tgz", 89 | "integrity": "sha512-eJI+QitXlwcgiZwNNSRbqsjeZMp5shyajMR81RZCqeW0ZDEj4zU9tpd4nTh/1JsBiKbF8d08FCewiipDmVIYjg==", 90 | "requires": { 91 | "opencollective-postinstall": "^2.0.0", 92 | "tz-offset": "0.0.1" 93 | } 94 | }, 95 | "opencollective-postinstall": { 96 | "version": "2.0.3", 97 | "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", 98 | "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==" 99 | }, 100 | "tz-offset": { 101 | "version": "0.0.1", 102 | "resolved": "https://registry.npmjs.org/tz-offset/-/tz-offset-0.0.1.tgz", 103 | "integrity": "sha512-kMBmblijHJXyOpKzgDhKx9INYU4u4E1RPMB0HqmKSgWG8vEcf3exEfLh4FFfzd3xdQOw9EuIy/cP0akY6rHopQ==" 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "automated-nodejs-backups", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node index.js" 7 | }, 8 | "dependencies": { 9 | "@getvim/execute": "^1.0.0", 10 | "axios": "^0.21.0", 11 | "dotenv": "^8.2.0", 12 | "form-data": "^3.0.0", 13 | "gzipme": "^1.0.0", 14 | "node-cron": "^2.0.3" 15 | } 16 | } 17 | --------------------------------------------------------------------------------