DEV Community

Asif Zubayer Palak
Asif Zubayer Palak

Posted on

Fullstackopen Summary Part 3

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Implement the server code

In order to handle server side development,
we need to use express
To install it, run:

npm install express
Enter fullscreen mode Exit fullscreen mode

Next, in index.js file, import the express library and use it.

const express = require('express')
const app = express()
Enter fullscreen mode Exit fullscreen mode

Listening on a port

const PORT=3001
app.listen(PORT, ()=> {
  console.log(`Server running on port ${PORT}`)
})
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

To observe live changes in the server use:

nodemon index.js
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

This allows sending data to the backend in json format

app.post('/api/notes', (request, response) =>{
  const note = request.body  
  response.json(note)
})
Enter fullscreen mode Exit fullscreen mode

Middlewares

Middlewares are functions used for handling request and response objects.
Used by the express server like this:

app.use(middleware)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

In index.js

const cors = require('cors')
app.use(cors())
Enter fullscreen mode Exit fullscreen mode

Production Build

To deploy the app, we must first create the production build which is made by

npm run build
Enter fullscreen mode Exit fullscreen mode

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'))
Enter fullscreen mode Exit fullscreen mode

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,      
    },    
  }  
 },
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

and to implement it, you need to go to the server side and add the following:

const mongoose = require('mongoose')
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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()
})
Enter fullscreen mode Exit fullscreen mode

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)
  })
})
Enter fullscreen mode Exit fullscreen mode

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
  }
})
Enter fullscreen mode Exit fullscreen mode

Once you save this configuration, you need to export it by:

module.exports = mongoose.model('Object',schema)
Enter fullscreen mode Exit fullscreen mode

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))})
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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))
})
Enter fullscreen mode Exit fullscreen mode

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))
})
Enter fullscreen mode Exit fullscreen mode

Enforce Validation

const noteSchema = new mongoose.Schema({
  content: {    
    type: String,    
    minLength: 5,    
    required: true  
  },  
  important: Boolean
})
Enter fullscreen mode Exit fullscreen mode

Add this to errorhandler

else if (error.name === 'ValidationError') {    
  return response.status(400).json({ error: error.message })  }
Enter fullscreen mode Exit fullscreen mode

Add this to the update request:

 Note.findByIdAndUpdate(
    request.params.id, 
    { content, important },    
    { new: true, runValidators: true, context: 'query' }  
) 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)