Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Working with TypeScript, Next.js, and Socket.io

I'm starting a small project that uses TypeScript, Next.js, and Socket.io. I don't understand how to tell TypeScript that I am kind of "merging" these two. For example, here are my files.

/api/socket.ts:

import { NextApiRequest, NextApiResponse } from 'next'
import { Server } from 'socket.io'

const SocketHandler = (req: NextApiRequest, res: NextApiResponse) => {
  if (res.socket.server.io) {
    console.log('Socket is already running.')
  } else {
    console.log('Socket is initializing...')

    const io = new Server(res.socket.server)
    res.socket.server.io = io

    io.on('connection', (socket) => {
      socket.on('input-change', (msg) => {
        socket.broadcast.emit('update-input', msg)
      })
    })
  }

  res.end()
}

export default SocketHandler

/components/client.tsx:

import { useEffect, useState } from 'react'
import io from 'socket.io-client'

import type { ChangeEvent } from 'react'
import type { Socket } from 'socket.io-client'

let socket: undefined | Socket

export default function Client() {
  const [input, setInput] = useState('')
  useEffect(() => {
    socketInitializer()
  }, [])

  const socketInitializer = async () => {
    fetch('/api/socket')
    socket = io()

    socket.on('connect', () => {
      console.log('connected')
    })

    socket.on('update-input', (msg) => {
      setInput(msg)
    })
  }

  const onChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
    setInput(e.target.value)

    if (socket !== undefined) {
      socket.emit('input-change', e.target.value)
    }
  }

  return (
    <input
      placeholder="Type something"
      value={input}
      onChange={onChangeHandler}
    />
  )
}

And this code is working. But I'm getting all kinds of warnings / errors, like "Property 'server' does not exist on type 'Socket'", "Object is possibly 'null'." (on res itself).

I understand the main issue is TypeScript does not know I am adding .io on the res.socket.server object. But what I don't understand is A) how to tell it that I am adding that .io, B) why res and socket are possibly null, and C) why .server does not exist on res.socket, according to TypeScript.

I just need some direction and possibly higher-level explanation of how to tackle this. I think I need a .d.ts file, or maybe I just a new interface, but I am not really sure how to properly write a new interface without over-riding types that are already in place.

like image 414
graysonlee123 Avatar asked Jun 09 '26 23:06

graysonlee123


1 Answers

I found an answer. I don't know if it is the correct way of going about it, but this is what I did.

First I made new TypeScript interfaces, starting with the .io property, and moved up all the way back to res. All of these extend what NextApiResponse already is, and just add new properties.

import type { Server as HTTPServer } from 'http'
import type { NextApiRequest, NextApiResponse } from 'next'
import type { Socket as NetSocket } from 'net'
import type { Server as IOServer } from 'socket.io'

interface SocketServer extends HTTPServer {
  io?: IOServer | undefined
}

interface SocketWithIO extends NetSocket {
  server: SocketServer
}

interface NextApiResponseWithSocket extends NextApiResponse {
  socket: SocketWithIO
}

Then I just assigned my new NextApiResponseWithSocket to the res parameter.

const SocketHandler = (_: NextApiRequest, res: NextApiResponseWithSocket) => { ... }

If there is a better way to do this, I would love to know.

like image 190
graysonlee123 Avatar answered Jun 11 '26 11:06

graysonlee123