diff --git a/server/proxy/streams_test.go b/server/proxy/streams_test.go
index 2b5b2212b..90c6ae5db 100644
--- a/server/proxy/streams_test.go
+++ b/server/proxy/streams_test.go
@@ -72,8 +72,7 @@ func getReader(t *testing.T, in []byte, errResult error) reader {
 	}
 }
 
-func getAny(t *testing.T, data []byte) *any.Any {
-	in := []byte{104, 101, 108, 108, 111}
+func getAny(t *testing.T, in []byte) *any.Any {
 	value, err := ptypes.MarshalAny(&streamsv1.BytesMessage{
 		Type:  streamsv1.IOStream_STDOUT,
 		Value: in,
diff --git a/server/proxy/streamwriter.go b/server/proxy/streamwriter.go
deleted file mode 100644
index 8cd9718cc..000000000
--- a/server/proxy/streamwriter.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package proxy
-
-import (
-	"io"
-
-	v1 "github.com/docker/api/protos/containers/v1"
-)
-
-type streamWriter struct {
-	stream v1.Containers_LogsServer
-}
-
-func newStreamWriter(stream v1.Containers_LogsServer) io.Writer {
-	return &streamWriter{
-		stream: stream,
-	}
-}
-
-func (w *streamWriter) Write(p []byte) (n int, err error) {
-	return len(p), w.stream.SendMsg(&v1.LogsResponse{
-		Logs: p,
-	})
-}
diff --git a/tests/node-client/build.sh b/tests/node-client/build.sh
index 44b87408d..66e80a562 100755
--- a/tests/node-client/build.sh
+++ b/tests/node-client/build.sh
@@ -3,8 +3,10 @@ node_modules/.bin/grpc_tools_node_protoc \
     --grpc_out=generate_package_definition:./grpc \
     -I ../../protos/contexts/v1 \
     -I ../../protos/containers/v1 \
+    -I ../../protos/streams/v1 \
     ../../protos/contexts/v1/*.proto \
-    ../../protos/containers/v1/*.proto
+    ../../protos/containers/v1/*.proto \
+    ../../protos/streams/v1/*.proto
 
 # generate d.ts codes
 protoc \
@@ -12,5 +14,7 @@ protoc \
     --ts_out=generate_package_definition:./grpc \
     -I ../../protos/contexts/v1 \
     -I ../../protos/containers/v1 \
+    -I ../../protos/streams/v1 \
     ../../protos/contexts/v1/*.proto \
-    ../../protos/containers/v1/*.proto
+    ../../protos/containers/v1/*.proto \
+    ../../protos/streams/v1/*.proto
diff --git a/tests/node-client/exec.ts b/tests/node-client/exec.ts
new file mode 100644
index 000000000..9e657f486
--- /dev/null
+++ b/tests/node-client/exec.ts
@@ -0,0 +1,85 @@
+import * as grpc from "@grpc/grpc-js";
+import * as readline from "readline";
+import * as google_protobuf_any_pb from "google-protobuf/google/protobuf/any_pb.js";
+
+import * as continersPb from "./grpc/containers_grpc_pb";
+import { IContainersClient } from "./grpc/containers_grpc_pb";
+import { ExecRequest, ExecResponse, LogsRequest } from "./grpc/containers_pb";
+
+import * as streamsPb from "./grpc/streams_grpc_pb";
+import { IStreamingClient } from "./grpc/streams_grpc_pb";
+import { BytesMessage } from "./grpc/streams_pb";
+
+let address = process.argv[3] || "unix:///tmp/backend.sock";
+
+const ContainersServiceClient = grpc.makeClientConstructor(
+  continersPb["com.docker.api.protos.containers.v1.Containers"],
+  "ContainersClient"
+);
+
+const client = (new ContainersServiceClient(
+  address,
+  grpc.credentials.createInsecure()
+) as unknown) as IContainersClient;
+
+const StreamsServiceClient = grpc.makeClientConstructor(
+  streamsPb["com.docker.api.protos.streams.v1.Streaming"],
+  "StreamsClient"
+);
+
+let streamClient = (new StreamsServiceClient(
+  address,
+  grpc.credentials.createInsecure()
+) as unknown) as IStreamingClient;
+
+let backend = process.argv[2] || "moby";
+let containerId = process.argv[3];
+const meta = new grpc.Metadata();
+meta.set("CONTEXT_KEY", backend);
+
+// Get the stream
+const stream = streamClient.newStream();
+
+stream.on("metadata", (m: grpc.Metadata) => {
+  let req = new ExecRequest();
+  req.setCommand("/bin/bash");
+  req.setStreamId(m.get("id")[0] as string);
+  req.setId(containerId);
+  req.setTty(true);
+
+  client.exec(req, meta, (err: any, _: ExecResponse) => {
+    if (err != null) {
+      console.error(err);
+      return;
+    }
+    process.exit();
+  });
+});
+
+readline.emitKeypressEvents(process.stdin);
+process.stdin.setRawMode(true);
+
+process.stdin.on("keypress", (str, key) => {
+  const mess = new BytesMessage();
+  const a = new Uint8Array(key.sequence.length);
+  for (let i = 0; i <= key.sequence.length; i++) {
+    a[i] = key.sequence.charCodeAt(i);
+  }
+
+  mess.setValue(a);
+
+  const any = new google_protobuf_any_pb.Any();
+  any.pack(
+    mess.serializeBinary(),
+    "type.googleapis.com/com.docker.api.protos.streams.v1.BytesMessage"
+  );
+  stream.write(any);
+});
+
+stream.on("data", (chunk: any) => {
+  const m = chunk.unpack(
+    BytesMessage.deserializeBinary,
+    "com.docker.api.protos.streams.v1.BytesMessage"
+  ) as BytesMessage;
+  process.stdout.write(m.getValue());
+});
diff --git a/tests/node-client/package.json b/tests/node-client/package.json
index 91f01166f..0da3f59d7 100644
--- a/tests/node-client/package.json
+++ b/tests/node-client/package.json
@@ -6,12 +6,13 @@
   "scripts": {
     "start": "ts-node index.ts",
     "prestart": "./build.sh"
-  },
+    },
   "dependencies": {
     "@grpc/grpc-js": "^1.0.3",
     "grpc": "^1.24.2",
     "grpc-tools": "^1.8.1",
     "grpc_tools_node_protoc_ts": "^3.0.0",
+    "readline": "^1.3.0",
     "ts-node": "^8.9.1",
     "typescript": "^3.8.3"
   }
diff --git a/tests/node-client/types.d.ts b/tests/node-client/types.d.ts
new file mode 100644
index 000000000..bcf05fe04
--- /dev/null
+++ b/tests/node-client/types.d.ts
@@ -0,0 +1 @@
+declare module "google-protobuf/google/protobuf/any_pb.js";
diff --git a/tests/node-client/yarn.lock b/tests/node-client/yarn.lock
index 467f99359..5f0ba9a42 100644
--- a/tests/node-client/yarn.lock
+++ b/tests/node-client/yarn.lock
@@ -1653,6 +1653,11 @@ readable-stream@^2.0.6, readable-stream@~2.3.6:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
+readline@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/readline/-/readline-1.3.0.tgz#c580d77ef2cfc8752b132498060dc9793a7ac01c"
+  integrity sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw=
+
 regex-not@^1.0.0, regex-not@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"