Home > Mobile >  How to return json from FastAPI (Backend) with websocket to vue (Frontend)
How to return json from FastAPI (Backend) with websocket to vue (Frontend)

Time:12-02

I have an application, in which the Frontend is through Vue and the backend is FastAPI, the communication is done through websocket.

Currently, the frontend allows the user to enter a term, which is sent to the backend to generate the autocomplete and also perform a search on a URL that returns a json. In which, I save this json in the frontend folder. After that, the backend returns the autocomplete data for the term in question to the frontend. The frontend displays the aucomplete along with the json data.

However, when I studied a little more, I noticed that there is a way to send the json returned by the request url to Vue (frontend), without having to save it locally, avoiding giving an error of not allowing to execute this process more than once.

My current code is as follows. For FastAPI (backend):

@app.websocket("/")
async def predict_question(websocket: WebSocket):
    await websocket.accept()
    while True:
        input_text = await websocket.receive_text()
        autocomplete_text = text_gen.generate_text(input_text)
        autocomplete_text = re.sub(r"[\([{})\]]", "", autocomplete_text)
        autocomplete_text = autocomplete_text.split()
        autocomplete_text = autocomplete_text[0:2]
        resp = req.get('www.description_url_search_=' input_text '')
        datajson = resp.json()
        with open('/home/user/backup/AutoComplete/frontend/src/data.json', 'w', encoding='utf-8') as f:
            json.dump(datajson, f, ensure_ascii=False, indent=4)
        await websocket.send_text(' '.join(autocomplete_text))

File App.vue (frontend):

<template>
  <div >
    <h1 style="color:#0072c6;">Title</h1>
    <p style="text-align:center; color:#0072c6;">
      Version 0.1
      <br>
    </p>
    <Autocomplete />
    <br>
  </div>
  <div style="color:#0072c6;">
    <JsonArq />
  </div>
  <div style="text-align:center;">
    <img src="./components/logo-1536.png" width=250 height=200 alt="Logo" >
  </div>
</template>

<script>
import Autocomplete from './components/Autocomplete.vue'
import JsonArq from './components/EstepeJSON.vue'
export default {
  name: 'App',
  components: {
    Autocomplete, 
    JsonArq: JsonArq
  }
}
</script>

<style>

  .main-container {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    font-family: 'Fredoka', sans-serif;
  }

  h1 {
    font-size: 3rem;
  }

  @import url('https://fonts.googleapis.com/css2?family=Fredoka&display=swap');
</style>

Autocomplete.vue file in the components directory:

<template>
<div >
  <div tabindex="1" @focus="setCaret" >
    <span @input="sendText" @keypress="preventInput" ref="editbar"  contenteditable="true"></span>
    <span  contenteditable="false">{{autoComplete}}</span>    
  </div>
</div>

</template>

<script>
export default {
  name: 'Autocomplete',
  data: function() {
    return {
      autoComplete: "",
      maxChars: 75,
      connection: null
    }
  },
  mounted() {
    const url = "ws://localhost:8000/"
    this.connection = new WebSocket(url);
    this.connection.onopen = () => console.log("connection established");
    this.connection.onmessage = this.receiveText;
  },
  methods: {
    setCaret() {
      const range= document.createRange()
      const sel = window.getSelection();
      const parentNode = this.$refs.editbar;

      if (parentNode.firstChild == undefined) {
        const emptyNode = document.createTextNode("");
        parentNode.appendChild(emptyNode);
      }

      range.setStartAfter(this.$refs.editbar.firstChild);
      range.collapse(true);
      sel.removeAllRanges();
      sel.addRange(range);
    },
    preventInput(event) {
      let prevent = false;      

      // handles capital letters, numbers, and punctuations input
      if (event.key == event.key.toUpperCase()) {
        prevent = true;
      }

      // exempt spacebar input
      if (event.code == "Space") {
        prevent = false;
      }

      // handle input overflow
      const nChars = this.$refs.editbar.textContent.length;
      if (nChars >= this.maxChars) {
        prevent = true;
      }

      if (prevent == true) {
        event.preventDefault();
      }
    },
    sendText() {
      const inputText = this.$refs.editbar.textContent;
      this.connection.send(inputText);
    },
    receiveText(event) {
      this.autoComplete = event.data;
    }
  }
}
</script>


EstepeJSON.ue file in the components directory:

<template>
  <div width="80%" v-for="regList in myJson" :key="regList" >
    <table>
        <thead>
          <tr>
            <th>Documento</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="countryList in regList[2]" :key="countryList">
            <td style="visibility: visible">{{ countryList}}</td>
          </tr>
        </tbody>
      </table>
    </div>

  <link
    rel="stylesheet"
    href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
  />
</template>

<script>
import json from "@/data.json";

export default {
  name: "EstepeJson",
  data() {
    return {
      myJson: json,
    };
  },
};
</script>

Example of the JSON returned by the URL:

[
{
"Title": "SOFT-STARTER", 
"Cod": "Produto: 15775931", 
"Description": "A soft-starter SSW7000 permite o controle de partida/parada e proteção de motores.", 
"Technical_characteristics": ["Corrente nominal", "600 A", "Tensão nominal", "4,16 kV", "Tensão auxiliar", "200-240 V", "Grau de proteção", "IP41", "Certificação", "CE"]
},
{
"Title": "SOFT-STARTER SSW", 
"Cod": "Produto: 14223395", 
"Description": "A soft-starter SSW7000 permite o controle de partida/parada e proteção de motores de indução trifásicos de média tensão.", 
"Technical_characteristics": ["Corrente nominal", "125 A", "Tensão nominal", "6,9 kV", "Tensão auxiliar", "200-240 V", "Grau de proteção", "IP54/NEMA12", "Certificação", "CE"]
}
]

CodePudding user response:

Just convert your data to a json string with json.dumps(mydata)

CodePudding user response:

First, instead of using Python requests module (which would block the event loop, see here for more details), I would highly suggest you use httpx, which offers an async API as well. Have a look at this answer and this answer for more details and working examples.

Second, to send data as JSON, you need to use await websocket.send_json(data), as explained in Starlette documentation. As shown in Starlette's websockets source code, Starlette/FastAPI will use text = json.dumps(data) (to serialise the data you passed) when calling send_json() function. Hence, you need to pass a Python dict object. Similar to requests, in httpx you can call the .json() method on the response object to get the response data as a dictionary, and then pass the data to send_json().

Example

from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse
import httpx

app = FastAPI()

html = """
<!DOCTYPE html>
<html>
    <head>
        <title>Chat</title>
    </head>
    <body>
        <h1>WebSocket Chat</h1>
        <form action="" onsubmit="sendMessage(event)">
            <input type="text" id="messageText" autocomplete="off"/>
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            var ws = new WebSocket("ws://localhost:8000/ws");
            ws.onmessage = function(event) {
                var messages = document.getElementById('messages')
                var message = document.createElement('li')
                var content = document.createTextNode(event.data)
                message.appendChild(content)
                messages.appendChild(message)
            };
            function sendMessage(event) {
                var input = document.getElementById("messageText")
                ws.send(input.value)
                input.value = ''
                event.preventDefault()
            }
        </script>
    </body>
</html>
"""


@app.get('/')
async def get():
    return HTMLResponse(html)
    

@app.websocket('/ws')
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        # here use httpx to issue a request as demonstrated in the linked answers above
        # r = await client.send(... 
        await websocket.send_json(r.json())
  • Related