This post contains the summary of what I learnt in fullstackopen (Part 3)
Preparing the backend
Create a directory for your backend and use
npm init
to create a package.json
file.
Create a file called index.js
to implement your backend code and to run it, you need to use
node index.js
Implement the server code
In order to handle server side development,
we need to use express
To install it, run:
npm install express
Next, in index.js file, import the express library and use it.
const express = require('express')
const app = express()
Listening on a port
const PORT=3001
app.listen(PORT, ()=> {
console.log(`Server running on port ${PORT}`)
})
GET Request to show on local server
let data = [ ... data in json format ... ]
app.get('/', (request, response) => {
response.send('<h1>Hello World!</h1>')
})
app.get('/api/data', (request, response) => {
response.json(data)
})
Observe live changes in the server
In order to observe live changes in the server, we need to use nodemon
npm install nodemon --save-dev
To observe live changes in the server use:
nodemon index.js
Defining parameters for routes
app.get('/api/notes/:id', (request, response) => {
const id = request.params.id
const note = notes.find(note => note.id === id)
response.json(note)
})
request.params.id
contains the parameter id from the route url.
Emulate requests to the backend
Use POSTMAN to simulate requests to the backend.
Posting data to the backend
app.use(express.json())
This allows sending data to the backend in json format
app.post('/api/notes', (request, response) =>{
const note = request.body
response.json(note)
})
Middlewares
Middlewares are functions used for handling request and response objects.
Used by the express server like this:
app.use(middleware)
CORS
In order to allow the client to access data from a server hosted on a different website or server, express needs to enable cors.
In terminal:
npm install cors
In index.js
const cors = require('cors')
app.use(cors())
Production Build
To deploy the app, we must first create the production build which is made by
npm run build
which creates a folder called dist
containing the build files.
Once the production build has been run, you can move it to the backend folder and then allow the backend to use the static content using:
app.use(express.static('dist'))
Proxy
We can use proxy to shorten the base url link since the backend is hosted on another url.
To do so, go to vite.config.js
file and add the following:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
},
}
},
})
Saving data to mongodb
Go to mongodb atlas to create a cluster and get a mongodb uri like:
mongodb+srv://fullstack:<password>@cluster0.o1opl.mongodb.net/?retryWrites=true&w=majority
To use the mongodb database, we need to use a node package called mongoose which can be installed using
npm install mongoose
and to implement it, you need to go to the server side and add the following:
const mongoose = require('mongoose')
To use the specific mongodb cluster, we need to use the cluster's uri which should be saved as an environment variable in a .env
file.
To set it up, you need to install dotenv
and import it using:
require('dotenv').config()
let's say the variable name is MONGO_URI
, so to use it, you need to access it by process.env.MONGO_URI
.
Configuring the schema
mongoose.set('strictQuery',false)
ensures that only the items stated in the schema gets saved into the mongodb database.
Then we connect the mongodb database to our backend using:
mongoose.connect(process.env.MONGO_URI)
Next, we declare a schema to set the basis of the data that our database will hold using:
const schema = new mongoose.Schema({
item_1: datatype,
item_2: datatype,
....
})
const Object = mongoose.model('Object',schema)
<create an entry using that schema, this part is demonstrated later on>
entry.save().then(res => {
mongoose.connection.close()
})
Fetching items from the database
We previously made an instance called Object
which followed the schema we made.
In order to get all the entries in that object, we use:
app.get('/api/data'.(req,res) => {
Object.find({}).then(result=>{
res.json(result)
})
})
Transforming mongodb output
The response from the mongodb database comes with some default values such as _id
and __v
.
These needs to be transformed into attributes that are cleaner.
Implement that by using:
schema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString()
delete returnedObject._id
delete returnedObject.__v
}
})
Once you save this configuration, you need to export it by:
module.exports = mongoose.model('Object',schema)
Error Handling
We can handle the errors in a middleware, in the middleware's HTTP request, you can do the following:
app.get('/api/notes/:id', (request, response, next) => { Note.findById(request.params.id)
.then(note => {
...
.catch(error => next(error))})
the next
function sends the error down the next routes till it reaches the errorhandler middleware.
You can initiate the errorhandler middleware like:
const errorHandler = (error, request, response, next) => {
console.error(error.message)
if (error.name === 'CastError') {
return response.status(400).send({ error: 'malformatted id' })
}
next(error)
}
// this has to be the last loaded middleware.
app.use(errorHandler)
The order of middleware loading
app.use(express.static('dist'))
app.use(express.json())
app.use(requestLogger)
app.post('/api/notes', (request, response) => {
const body = request.body
// ...
})
const unknownEndpoint = (request, response) => {
response.status(404).send({ error: 'unknown endpoint' })
}
// handler of requests with unknown endpoint
app.use(unknownEndpoint)
const errorHandler = (error, request, response, next) => {
// ...
}
// handler of requests with result to errors
app.use(errorHandler)
Delete and Update Requests
Delete
app.delete('/api/notes/:id', (request, response, next) => {
Note.findByIdAndRemove(request.params.id)
.then(result => {
response.status(204).end()
})
.catch(error => next(error))
})
Update
app.put('/api/notes/:id', (request, response, next) => {
const body = request.body
const note = {
content: body.content,
important: body.important,
}
Note.findByIdAndUpdate(request.params.id, note, { new: true })
.then(updatedNote => {
response.json(updatedNote)
})
.catch(error => next(error))
})
Enforce Validation
const noteSchema = new mongoose.Schema({
content: {
type: String,
minLength: 5,
required: true
},
important: Boolean
})
Add this to errorhandler
else if (error.name === 'ValidationError') {
return response.status(400).json({ error: error.message }) }
Add this to the update request:
Note.findByIdAndUpdate(
request.params.id,
{ content, important },
{ new: true, runValidators: true, context: 'query' }
)
Top comments (0)