Skip to content

Commit 522a740

Browse files
committed
docs(streams): adds docs for streaming files with StreamableFiles
1 parent 15090cb commit 522a740

File tree

6 files changed

+97
-19
lines changed

6 files changed

+97
-19
lines changed

content/techniques/streaming-files.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
### Streaming Files
2+
3+
There won't always be a time where you're sending back JSON or string responses. There may be times where you'd like to send back a file from the server to the client. In an express application, you may use something like
4+
5+
```ts
6+
app.get('send-file', (req, res) => {
7+
createReadStream(path(process.cwd(), 'package.json')).pipe(res);
8+
});
9+
```
10+
11+
to manage sending the file back. This is still doable in Nest, by injecting `@Res()` in the controller, but in doing so you end up losing access to your post-controller interceptor logic. To handle this, you can return a `StreamableFile` instance and under the hood Nest will take care of piping the response.
12+
13+
#### Streamable File
14+
15+
A `StreamableFile` is as class that holds onto the stream that is to be returned. To create a new `StreamableFile`, you can pass either a `Buffer` or a `Stream` to the `StreamableFile` constructor.
16+
17+
> info **hint** The `StreamableFile` class can be imported from `@nestjs/common`.
18+
19+
#### Cross Platform Support
20+
21+
Fastify, by default, can support sending files without needing to `stream.pipe(res)`, so you don't need to use the `StreamableFile` class. However, Nest supports the use of `StreamableFile` in both platform types, so if you end up switching between Express and Fastify there's no need to worry about compatibility between the two engines.
22+
23+
#### Example
24+
25+
```ts
26+
import { Controller, Get, StreamableFile } from '@nestjs/common';
27+
import { createReadStream } from 'fs';
28+
import { join } from 'path';
29+
30+
@Controller('file')
31+
export class FileController {
32+
@Get()
33+
getFile(): StreamableFile {
34+
const file = createReadStream(join(process.cwd(), 'package.json'));
35+
return new StreamableFile(file);
36+
}
37+
}
38+
```
39+
40+
This is of course a simple example of returning the `package.json` as a file instead of a JSON, but the idea extends out naturally to images, documents, and any other file type.
41+

src/app/homepage/menu/menu.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export class MenuComponent implements OnInit {
9595
{ title: 'Events', path: '/techniques/events' },
9696
{ title: 'Compression', path: '/techniques/compression' },
9797
{ title: 'File upload', path: '/techniques/file-upload' },
98+
{ title: 'Streaming Files', path: '/techniques/streaming-files' },
9899
{ title: 'HTTP module', path: '/techniques/http-module' },
99100
{ title: 'Session', path: '/techniques/session' },
100101
{ title: 'Model-View-Controller', path: '/techniques/mvc' },

src/app/homepage/pages/cli/scripts/scripts.component.html

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ <h4 appAnchor id="package-scripts"><span>Package scripts</span></h4>
3333
<p>When you run <code>nest new</code>, or clone the <a rel='nofollow' target='_blank' href="https://github.com/nestjs/typescript-starter">typescript starter</a>, Nest populates the new project&#39;s <code>package.json</code> scripts with commands like <code>build</code> and <code>start</code>. It also installs the underlying compiler tools (such as <code>typescript</code>) as <strong>dev dependencies</strong>.</p>
3434
<p>You run the build and execute scripts with commands like:</p>
3535
<pre><code class="language-bash">
36-
$ npm run build</code></pre>
36+
$ npm run build
37+
</code></pre>
3738
<p>and</p>
3839
<pre><code class="language-bash">
39-
$ npm run start</code></pre>
40+
$ npm run start
41+
</code></pre>
4042
<p>These commands use npm&#39;s script running capabilities to execute <code>nest build</code> or <code>nest start</code> using the <strong>locally installed</strong> <code>nest</code> binary. By using these built-in package scripts, you have full dependency management over the Nest CLI commands*. This means that, by following this <strong>recommended</strong> usage, all members of your organization can be assured of running the same version of the commands.</p>
4143
<p>*This applies to the <code>build</code> and <code>start</code> commands. The <code>nest new</code> and <code>nest generate</code> commands aren&#39;t part of the build/execute pipeline, so they operate in a different context, and do not come with built-in <code>package.json</code> scripts.</p>
4244
<p>For most developers/teams, it is recommended to utilize the package scripts for building and executing their Nest projects. You can fully customize the behavior of these scripts via their options (<code>--path</code>, <code>--webpack</code>, <code>--webpackPath</code>) and/or customize the <code>tsc</code> or webpack compiler options files (e.g., <code>tsconfig.json</code>) as needed. You are also free to run a completely custom build process to compile the TypeScript (or even to execute TypeScript directly with <code>ts-node</code>).</p>
@@ -47,13 +49,15 @@ <h4 appAnchor id="migration"><span>Migration</span></h4>
4749
<pre><code class="language-bash">
4850
$ npm install -g @nestjs/cli
4951
$ cd /some/project/root/folder
50-
$ npm install -D @nestjs/cli</code></pre>
52+
$ npm install -D @nestjs/cli
53+
</code></pre>
5154
<p>You can then replace the <code>scripts</code> defined in <code>package.json</code> with the following ones:</p>
5255
<pre><code class="language-typescript">
5356
&quot;build&quot;: &quot;nest build&quot;,
5457
&quot;start&quot;: &quot;nest start&quot;,
5558
&quot;start:dev&quot;: &quot;nest start --watch&quot;,
56-
&quot;start:debug&quot;: &quot;nest start --debug --watch&quot;,</code></pre>
59+
&quot;start:debug&quot;: &quot;nest start --debug --watch&quot;,
60+
</code></pre>
5761

5862
</div>
5963

src/app/homepage/pages/microservices/custom-transport/custom-transport.component.html

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ <h4 appAnchor id="creating-a-strategy"><span>Creating a strategy</span></h4>
3939
* This method is triggered on application shutdown.
4040
*/
4141
close() &#123;&#125;
42-
&#125;</code></pre>
42+
&#125;
43+
</code></pre>
4344
<blockquote class="
4445
warning "><strong>Warning</strong> Please, note we won&#39;t be implementing a fully-featured Google Cloud Pub/Sub server in this chapter as this would require diving into transporter specific technical details.
4546
</blockquote>
@@ -53,7 +54,8 @@ <h4 appAnchor id="creating-a-strategy"><span>Creating a strategy</span></h4>
5354
&#123;
5455
strategy: new GoogleCloudPubSubServer(),
5556
&#125;,
56-
);</code></pre>
57+
);
58+
</code></pre>
5759
<p>Basically, instead of passing the normal transporter options object with <code>transport</code> and <code>options</code> properties, we pass a single property, <code>strategy</code>, whose value is an instance of our custom transporter class.</p>
5860
<p>Back to our <code>GoogleCloudPubSubServer</code> class, in a real-world application, we would be establishing a connection to our message broker/external service and registering subscribers/listening to specific channels in <code>listen()</code> method (and then removing subscriptions &amp; closing the connection in the <code>close()</code> teardown method),
5961
but since this requires a good understanding of how Nest microservices communicate with each other, we recommend reading this <a rel='nofollow' target='_blank' href="https://dev.to/nestjs/part-1-introduction-and-setup-1a2l">article series</a>.
@@ -63,17 +65,20 @@ <h4 appAnchor id="creating-a-strategy"><span>Creating a strategy</span></h4>
6365
@MessagePattern(&#39;echo&#39;)
6466
echo(@Payload() data: object) &#123;
6567
return data;
66-
&#125;</code></pre>
68+
&#125;
69+
</code></pre>
6770
<p>This message handler will be automatically registered by Nest runtime. With <code>Server</code> class, you can see what message patterns have been registered and also, access and execute the actual methods that were assigned to them.
6871
To test this out, let&#39;s add a simple <code>console.log</code> inside <code>listen()</code> method before <code>callback</code> function is called:</p>
6972
<pre><code class="language-typescript">
7073
listen(callback: () =&gt; void) &#123;
7174
console.log(this.messageHandlers);
7275
callback();
73-
&#125;</code></pre>
76+
&#125;
77+
</code></pre>
7478
<p>After your application restarts, you&#39;ll see the following log in your terminal:</p>
7579
<pre><code class="language-typescript">
76-
Map &#123; &#39;echo&#39; =&gt; [AsyncFunction] &#123; isEventHandler: false &#125; &#125;</code></pre>
80+
Map &#123; &#39;echo&#39; =&gt; [AsyncFunction] &#123; isEventHandler: false &#125; &#125;
81+
</code></pre>
7782
<blockquote class="
7883
info "><strong>Hint</strong> If we used the <code>@EventPattern</code> decorator, you would see the same output, but with the <code>isEventHandler</code> property set to <code>true</code>.
7984
</blockquote>
@@ -84,10 +89,12 @@ <h4 appAnchor id="creating-a-strategy"><span>Creating a strategy</span></h4>
8489
const echoHandler = this.messageHandlers.get(&#39;echo&#39;);
8590
console.log(await echoHandler(&#39;Hello world!&#39;));
8691
callback();
87-
&#125;</code></pre>
92+
&#125;
93+
</code></pre>
8894
<p>Once we execute the <code>echoHandler</code> passing an arbitrary string as an argument (<code>&quot;Hello world!&quot;</code> here), we should see it in the console:</p>
8995
<pre><code class="language-json">
90-
Hello world!</code></pre>
96+
Hello world!
97+
</code></pre>
9198
<p>Which means that our method handler was properly executed.</p>
9299
<h4 appAnchor id="client-proxy"><span>Client proxy</span></h4>
93100
<p>As we mentioned in the first section, you don&#39;t necessarily need to use the <code>@nestjs/microservices</code> package to create microservices, but if you decide to do so and you need to integrate a custom strategy, you will need to provide a &quot;client&quot; class too.</p>
@@ -106,7 +113,8 @@ <h4 appAnchor id="client-proxy"><span>Client proxy</span></h4>
106113
packet: ReadPacket&lt;any&gt;,
107114
callback: (packet: WritePacket&lt;any&gt;) =&gt; void,
108115
): Function &#123;&#125;
109-
&#125;</code></pre>
116+
&#125;
117+
</code></pre>
110118
<blockquote class="
111119
warning "><strong>Warning</strong> Please, note we won&#39;t be implementing a fully-featured Google Cloud Pub/Sub client in this chapter as this would require diving into transporter specific technical details.
112120
</blockquote>
@@ -140,18 +148,21 @@ <h4 appAnchor id="client-proxy"><span>Client proxy</span></h4>
140148

141149
return () =&gt; console.log(&#39;teardown&#39;);
142150
&#125;
143-
&#125;</code></pre>
151+
&#125;
152+
</code></pre>
144153
<p>With this in place, let&#39;s create an instance of <code>GoogleCloudPubSubClient</code> class and run the <code>send()</code> method (which you might have seen in earlier chapters), subscribing to the returned observable stream.</p>
145154
<pre><code class="language-typescript">
146155
const googlePubSubClient = new GoogleCloudPubSubClient();
147156
googlePubSubClient
148157
.send(&#39;pattern&#39;, &#39;Hello world!&#39;)
149-
.subscribe((response) =&gt; console.log(response));</code></pre>
158+
.subscribe((response) =&gt; console.log(response));
159+
</code></pre>
150160
<p>Now, you should see the following output in your terminal:</p>
151161
<pre><code class="language-typescript">
152162
connect
153163
message: &#123; pattern: &#39;pattern&#39;, data: &#39;Hello world!&#39; &#125;
154-
Hello world! // &lt;-- after 5 seconds</code></pre>
164+
Hello world! // &lt;-- after 5 seconds
165+
</code></pre>
155166
<p>To test if our &quot;teardown&quot; method (which our <code>publish()</code> method returns) is properly executed, let&#39;s apply a timeout operator to our stream, setting it to 2 seconds to make sure it throws earlier then our <code>setTimeout</code> calls the <code>callback</code> function.</p>
156167
<pre><code class="language-typescript">
157168
const googlePubSubClient = new GoogleCloudPubSubClient();
@@ -161,7 +172,8 @@ <h4 appAnchor id="client-proxy"><span>Client proxy</span></h4>
161172
.subscribe(
162173
(response) =&gt; console.log(response),
163174
(error) =&gt; console.error(error.message),
164-
);</code></pre>
175+
);
176+
</code></pre>
165177
<blockquote class="
166178
info "><strong>Hint</strong> The <code>timeout</code> operator is imported from the <code>rxjs/operators</code> package.
167179
</blockquote>
@@ -170,14 +182,17 @@ <h4 appAnchor id="client-proxy"><span>Client proxy</span></h4>
170182
connect
171183
message: &#123; pattern: &#39;pattern&#39;, data: &#39;Hello world!&#39; &#125;
172184
teardown // &lt;-- teardown
173-
Timeout has occurred</code></pre>
185+
Timeout has occurred
186+
</code></pre>
174187
<p>To dispatch an event (instead of sending a message), use the <code>emit()</code> method:</p>
175188
<pre><code class="language-typescript">
176-
googlePubSubClient.emit(&#39;event&#39;, &#39;Hello world!&#39;);</code></pre>
189+
googlePubSubClient.emit(&#39;event&#39;, &#39;Hello world!&#39;);
190+
</code></pre>
177191
<p>And that&#39;s what you should see in the console:</p>
178192
<pre><code class="language-typescript">
179193
connect
180-
event to dispatch: &#123; pattern: &#39;event&#39;, data: &#39;Hello world!&#39; &#125;</code></pre>
194+
event to dispatch: &#123; pattern: &#39;event&#39;, data: &#39;Hello world!&#39; &#125;
195+
</code></pre>
181196

182197
</div>
183198

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
import { ChangeDetectionStrategy, Component } from '@angular/core';
3+
import { BasePageComponent } from '../../page/page.component';
4+
5+
@Component({
6+
selector: 'app-streaming-files',
7+
templateUrl: './streaming-files.component.html',
8+
changeDetection: ChangeDetectionStrategy.OnPush,
9+
})
10+
export class StreamingFilesComponent extends BasePageComponent {}

src/app/homepage/pages/techniques/techniques.module.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { SerializationComponent } from './serialization/serialization.component'
1818
import { ServerSentEventsComponent } from './server-sent-events/server-sent-events.component';
1919
import { SessionComponent } from './sessions/sessions.component';
2020
import { SqlComponent } from './sql/sql.component';
21+
import { StreamingFilesComponent } from './streaming-files/streaming-files.component';
2122
import { TaskSchedulingComponent } from './task-scheduling/task-scheduling.component';
2223
import { ValidationComponent } from './validation/validation.component';
2324

@@ -65,6 +66,11 @@ const routes: Routes = [
6566
component: FileUploadComponent,
6667
data: { title: 'File upload' },
6768
},
69+
{
70+
path: 'streaming-files',
71+
component: StreamingFilesComponent,
72+
data: { title: 'Streaming Files' },
73+
},
6874
{
6975
path: 'logger',
7076
component: LoggerComponent,
@@ -151,6 +157,7 @@ const routes: Routes = [
151157
ServerSentEventsComponent,
152158
SessionComponent,
153159
CookiesComponent,
160+
StreamingFilesComponent,
154161
],
155162
})
156163
export class TechniquesModule {}

0 commit comments

Comments
 (0)