How should Waterline v0.13 handle `required`, `defaultsTo`, `autoCreatedAt`, and `autoUpdatedAt`?
PUT YOUR BEARD IN MY MOUTH
Aqua Utopia|海の底で記憶を紡ぐ

祝日 / Permanent Vacation
i don't do bad sauce passes
Xuebing Du
Jules of Nature
cherry valley forever

Love Begins

Janaina Medeiros
tumblr dot com
Misplaced Lens Cap

JVL
art blog(derogatory)
noise dept.

izzy's playlists!
No title available
d e v o n
2025 on Tumblr: Trends That Defined the Year
Game of Thrones Daily

Kiana Khansmith

seen from Germany

seen from Poland

seen from United States

seen from Malaysia
seen from Canada

seen from Türkiye
seen from United States

seen from Italy
seen from United States

seen from Germany

seen from T1

seen from T1
seen from T1

seen from France

seen from Italy
seen from Malaysia
seen from Malaysia
seen from Russia
seen from Sri Lanka
seen from United States
@mikermcneil
How should Waterline v0.13 handle `required`, `defaultsTo`, `autoCreatedAt`, and `autoUpdatedAt`?
NODE_ENV=production
Note to self: Seriously consider the pros and cons of seizing control of the user's browser and playing Bootylicious by Destiny's Child if the generated app.js file in a Sails app detects that you are trying to run it directly without also setting the NODE_ENV environment variable to "production".
Assuming there was no fatal error, should the first argument to an asynchronous callback be `undefined` or `null`?
exposeLocalsToBrowser()
Just reached the next milestone on the road towards Sails v1: As of today, you can add a line of code to your HTML to directly expose data from your server-side view locals to client-side JavaScript, with built-in XSS attack prevention. This eliminates the need to hand-roll your own decoding/encoding (or worse, forget). And it means you don't need to send a bunch of extra AJAX requests to safely get the data you need onto the web page. (For more background on the use case, check out this 2012 post from Dan Webb, Twitter's former engineering manager: https://blog.twitter.com/2012/improving-performance-on-twittercom)
The new injector in Sails works by injecting a script tag into the raw HTML source, and stuffing the escaped version of your data (with all unsafe HTML characters encoded) inside. Then, by default, it also injects a pre-minified client-side unescape function (compatible with IE9 and up) that deals with de-scrambling HTML entities back into the unsafe characters from whence they came. For more info, check out the official Sails.js roadmap and the relevant proposal PR. Or, to take it for a spin, check out https://github.com/mikermcneil/expose-locals-to-browser-xss-test-app
omg is that a memory leak?
I've spent a lot of time testing Node.js/Sails.js apps and modules for memory leaks (with the help of @particlebanana, @sgress454, and others). Fortunately, true memory leaks are pretty rare.
But I have encountered memory leaks in some application-level code at least once. I say "at least" because the rest of the Sails.js team and I have found WAAAAAYY more things that we thought were memory leaks, but actually weren't.
Which is good, I guess. But possibly even more annoying.
So I wanted to jot down a bit about what I've learned about memory leaks over the past five years -- and specifically the process that I use to diagnose whether one exists in a Node.js/Sails.js app.
1. Eliminate variables
Do everything you can to simplify your test environment.
Before you get started trying to diagnose a memory leak, you should rule out as many problems as possible. In a Node.js/Sails.js app, that starts with configuring recommended production settings.
2. Assume you messed up
Accept the crushing reality that it's probably all your fault.
Next, check your app-level code (i.e. try to isolate the leak to a single endpoint). In my experience, it is very common w/ Node apps in general to end up with leaks from failing to handle errors (e.g. if you're using promises and forget to do a .catch()).
If you can't isolate the source of increasing memory usage to a particular endpoint or anything about your app code, then it's time to try replicating the leak in a brand new app running with the same recommended production settings, with no bells and whistles, with recommended production settings.
3. Remind yourself what a memory leak is, actually
Take a moment to mutter to yourself to make sure you remember what you're doing.
I have no idea if this is true for you or not; but when I'm working on a problem, I have to constantly remind myself of what I'm doing. Otherwise I forget. Or worse, I'll realize what I was doing didn't really make any sense anyway.
So let's take a second to do that.
"What are we doing?"
We're trying to figure out whether our Node process has a memory leak.
"Why are we doing it?"
Good question.
OK how do we know?!
A Node process will grow its memory usage until it decides it's time to run the garbage collector. Just because memory is going up continually does not mean there is a memory leak-- it means that the garbage collector has not run yet.
Sounds simple, right? But this tends to be where things break down. Until you've actually gone through the process of diagnosing a leak step by step, it's really hard to know wtf you're doing. And if you think you know wtf you're doing, you probably don't. (That was the case the first time I tried it anyway.)
So in the spirit of that, here are a few examples to give you some reps: (1 2 3 )
4. Run an experiment
Set up monitoring, recreate the supposedly leaky behavior, then use the garbage collector to verify that memory is never being reclaimed.
The only surefire way to diagnose a memory leak is to run your Node process with the --expose-gc flag enabled, to manually run the garbage collector , and then to compare the "valleys" of the chart. Here's a step by step guide:
4A. Expose gc endpoint
Expose a development-only endpoint that, when called, will run the garbage collector. For example: https://github.com/balderdashy/sails-hook-dev/blob/master/index.js#L190-L203 (you can also just install sails-hook-dev in your project).
4B. Monitor
Start up a program that monitors your Node process's memory. I recommend NodeSource.
4C. Lift
Lift your app with all recommended production settings (see deployment and scaling docs on sailsjs.org). But also make sure you tell Node.js to allow your code programmatic use of the garbage collector. For example, I like to do this by running:
NODE_ENV=production node --expose-gc app.js
4D. Do stuff or run load tests (round 1)
Now perform the behavior that you suspect will cause a memory leak. For example, that might be sending "POST" requests to a particular URL (use your app's UI or a tool like Postman). After about 30 minutes of this, if you take a look at the graphs, they might look slopey and bad, or they might flat and good. But that doesn't matter at all-- we have no idea about whether there are memory leaks at this point.
4E. Force garbage collector to run (round 1)
Now hit your endpoint that runs the garbage collector. You'll see the graphs drop dramatically after a moment. Take note of the lowest point in the graph (the "valley"). This is the amount of memory that the process is using, and that could not be reclaimed using the garbage collector. This is the memory where stuff you're actually using lives-- e.g. the require() cache, and local variables that are still in use. Be sure to take a screenshot of the graphs and memory usage in GB at this point.
4F. Do stuff or run load tests (round 2)
Now do exactly the same thing we did in step 4D again, for another 30 minutes.
4G. Force garbage collector to run (round 2)
Now do exactly the same thing we did in step 4E again. After a moment, if you notice that the "valley" in the graph is significantly higher than it was in step 4E, there might be something going on (there seems to be some amount of natural variation in what the garbage collector can actually reclaim-- in some cases this second "valley" is actually lower). Be sure to take a second screenshot of the graphs and memory usage in GB at this point.
Finally, if, after running through the steps above, it seems likely that there is a memory leak (i.e. the second "valley" was significantly higher than the first), then repeat steps 4F and 4G one more time to be sure.
5. Analyze your results
Check out the "valleys" in your memory usage graph (the spots right after the garbage collector ran). If they're getting higher and higher, you've got a memory leak.
If there is a memory leak, you can expect the second "valley" to be significantly higher than the first, and the third "valley" to be significantly higher than the second.
6. Cope
If there's a memory leak, fix it.
That's it. Whether there's a memory leak or not, you'll never get that hour back. But at least now you know.
-m
Originally posted on GitHub.
Sails v1 (First Look)
A first look at the new features, changes, and deprecations to expect in Sails v1.0:
Originally posted in our ROADMAP.md file on GitHub
Sails v1.0
This is an early list of some of the features, enhancements, and other improvements tentatively planned or already implemented for the v1.0 release of Sails. This is by no means a comprehensive changelog or release plan, and may exclude important additions, bug fixes, and documentation tasks. It is just a reference point with the highlights. But we're getting close.
Built-in Support for Database Projections (i.e. SELECT)
This is already implemented in Waterline, but not yet exposed in Sails.
We may do a minor release of Sails prior to v1.0 so that folks can take advantage of this today.
If you want to use Waterline 0.12 in your Sails app in the mean time, fork sails-hook-orm, upgrade its waterline dependency, then NPM install your forked version as a dependency in your Sails app project, and it will take effect automatically. (But be sure you are using the appropriate adapters-- tweet @particlebanana or @sailsjs if you have questions on that.)
Built-in Support for Dynamic Database Connections
Implemented via sails.hooks.orm.datastore()
See https://github.com/node-machine/driver-interface and https://github.com/particlebanana/waterline-query-docs/issues/2
API: sails.hooks.orm.datastore('foo').connect(during, afterDisconnecting)
Also: User.find().usingConnection(mySQLConnectionObtainedFromUsingRawDriver).exec();
Advanced Joins (using compiled statements based on Knex)
This will be usable prior to Sails v1 (and is available for experimental use today)
See https://github.com/particlebanana/waterline-query-docs/
Built-in Support for Native Database Transactions (for databases that support it)
See https://github.com/postmanlabs/sails-mysql-transactions/issues/26#issuecomment-191225758)
API is similar to above: sails.hooks.orm.datastore('foo').transaction(during, afterCommittingOrRollingBack)
Native Queries
Model-based usage like User.native() and User.query() will be deprecated.
Instead, native queries (e.g. SQL or Mongo queries) will be performed by accessing the appropriate datastore.
API is similar to above: sails.hooks.orm.datastore('foo').query(nativeQuery, afterFinished)
Nested create / nested update
Will be disabled by default.
Case sensitivity in criteria's where in Waterline find/findOne/count/update/destroy
Will be database-specific instead of normalized to be case-insensitive.
This is primarily in order to improve performance on PostgreSQL and to allow for more customizability with character sets/collations across all databases.
Automigrations
Will be moved out of Waterline and into sails-hook-orm.
Usage is unlikely to change.
Built-in XSS Prevention (exposeLocalsToBrowser() view helper)
See https://github.com/balderdashy/sails/pull/3522
Federated hooks (custom builds)
See https://github.com/balderdashy/sails/pull/3504
Validation errors in blueprints, res.jsonx(), & error handling in custom responses
Will be handled by calling res.badRequest() directly
The toJSON() function of errors will be called (since res.json will be used instead of res.jsonx)
https://github.com/balderdashy/sails/commit/b8c3813281a041c0b24db381b046fecfa81a14b7#commitcomment-18455430
Error handling (in general)
Default implementation of res.serverError() will continue to never send error data in production
But default impl of res.ok() and res.badRequest() will always send the provided argument as response data, even in production.
Default implementations of res.forbidden() and res.notFound() will no longer send a response body at all.
The default error handler in Sails (i.e. next(err)) will call res.serverError() instead of res.negotiate().
Support for res.negotiate() will likely still exist, but will log a warning.
For more details, see https://github.com/balderdashy/sails/commit/b8c3813281a041c0b24db381b046fecfa81a14b7#commitcomment-18455430
For historical context, see also [#3568] (https://github.com/balderdashy/sails/pull/3568)
JSONP support in blueprints
Will be deprecated (along with res.jsonx, as mentioned above)
CORS support is so widespread in browsers today (IE8 and up) that JSONP is rarely necessary-- and certainly isn't worth the complexity/weight in core. After upgrading to v1, if you want to implement support for JSONP within the blueprint API, it is still achievable by modifying the relevant default responses (api/responses/badRequest.js, api/responses/serverError.js, and api/responses/notFound.js) to use res.jsonp() instead of res.json() (or to determine which to use based on the value of a request param).
sails.config.environment and the NODE_ENV environment variable
Sails will no longer set the NODE_ENV environment variable automatically by default.
Apps will need to set NODE_ENV themselves in addition to sails.config.environment.
If NODE_ENV is set to "production but sails.config.environment is not specified, then sails.config.environment will still be set to "production" automatically.
But if both NODE_ENV and sails.config.environment are specified, then no changes will be made to either.
If sails.config.environment is set to "production" and the NODE_ENV environment variable is not also set to production, Sails will log a warning.
^^needs tests.
Better built-in support for command-line scripts that require access to the Sails app instance
https://github.com/treelinehq/machine-as-script/commits/master
sails-stdlib
Library of well-tested, well-documented, and officially supported modules for the most common everyday tasks in apps (e.g. password encryption)
Sails v0.12.2
Sails 0.12.2 will be published later today. At the moment, the pre-release is available when you run npm install sails@beta.
If anyone wants to try it out and let us know how it goes, the core team would greatly appreciate it!
The documentation has been updated for the newest release as well, and will be available on sailsjs.org as soon as cloudflare allows it. (Until then, you can find the un-cached version here.)
Oh, and in case you were wondering, this is definitely not an April Fool’s prank. That would be the boringest April Fools prank ever.
Warning: npm v3.3.12 may derp your deps
TL;DR – If you just installed a new version of Node, check your NPM version. If you’re using NPM v3.3.12 (the default for Node 5.5.0), you could run into issues when installing dependencies in your Sails app. Fortunately, NPM fixed this pretty fast, so all you should have to do is upgrade to the latest version of NPM (npm install -g npm@latest) and everything will be hunky dory.
Version 3.0 of NPM (the Node Package Monger) significantly changed the way dependencies are saved in Node apps. Previously, your project’s node_modules/ folder contained a deeply nested tree of subfolders, each representing a dependency of your project. Each one of those node_modules/ folders contains more dependencies, each containing their own node_modules/ folder to house their dependencies, and so on.
mySweetApp ├─┬ node_modules │ ├─┬ [email protected] (top level dep) │ │ ├── node_modules │ │ │ ├── [email protected] (2nd level dep) │ ├─┬ [email protected] (top level dep) │ │ ├── node_modules │ │ │ ├── [email protected] (2nd level dep) etc...
This allowed for some optimizations to be made when you ran npm install (a second-level dependency wouldn’t be installed if it matched an existing top-level dependency). But there were still a lot of redundant packages in the tree.
So NPM made some big changes. Beginning with version 3.0, all dependencies are now saved in the top-level node_modules/ folder, unless multiple versions of the same dependency are needed:
mySweetApp ├─┬ node_modules │ ├── [email protected] │ ├── [email protected] │ ├── [email protected] etc...
This has the potential for a great reduction in redundancy. If your app requires both packageA and packageB, and both of those require compatible versions of packageC, it will only be installed once (at the top level) instead of twice (in the node_modules/ folders of both packageA and packageB).
The NPM 3 paradigm shift has caused a few compatibility issues over time, but in the long run it was the right decision, since it reduces the amount of redundant data you need to download and store on your laptop or server when you run npm install. To make an omelette, you have to crack some eggs. Sometimes, those eggs have little red specks in them, but that’s OK.
A bug was introduced in version 3.3.10 that under some circumstances causes NPM to incorrectly remove a dependency from the top-level node_modules/ folder and place it further down in the tree, breaking any code that require()s that module at the top level. The bug was patched in version 3.4.1, and using that upgrade, installs work as expected.
But be aware: at the time of writing this, the latest stable release of Node (5.5.0) comes bundled with NPM version 3.3.12, which still has the bug. To check the version of NPM you have installed, run:
npm -v
If you’re using NPM v3.3.12, then you should upgrade to the patched release. To do so:
npm install -g npm@latest
Lean back, cross your legs, and enjoy the install.
Sincerely,
The Sails.js Team
P.S. After upgrading, you should be able to npm install in your app without further incident. But if you still see issues, it’s probably time to pull out all the stops:
First, adjust your body into a crouching position and hold on to your desk with one hand.
With the other hand, carefully peck out npm install --force on your keyboard.
Press <enter>, then watch the screen nervously. Try not to lose your balance. Hopefully, in a few seconds, all will be well. But if not, unleash the nuclear option with npm cache clear and then try force installing again.
If that doesn’t work, step away for a second. Listen to some TLC and pour yourself some sparkling grape juice. When you feel ready, continue to the next step. Only you will know when you’re ready.
With your mind refreshed, poke around a bit in the console output from NPM and see if anything jumps out. If you’re at a loss, head over to GitHub and open an issue in the Sails repo. One of us will help you out ASAP.
Installing dependencies in your Sails.js app using NPM v3.3.12
The latest breaking news about omelettes, TLC, sparkling grape juice, and NPM v3.3.12: http://blog.sailsjs.org/post/138232358567/warning-npm-v3312-may-derp-your-deps
This is an example of the new format for API reference pages on sailsjs.org.
This is an example of the new format for API reference pages on sailsjs.org. Expect to see this across the board in our reference docs over the next week. We've set up http://preview.sailsjs.org to automatically reflect changes merged into master in the sails-docs repo. This makes it easier for us to work with documentation and catch any formatting issues and typos. And actually, it seems like there might be an easy way for docs contributors to preview their work live as they're committing to their fork (i.e. before PRs are merged). I'll keep everyone posted-- hoping to get a chance to experiment with that this weekend.
Kitchen Confidential
Dear Sailsbot,
I’m looking to completely redecorate my kitchen after a sudden windfall (i.e. death in the family). I still love the look of granite countertops, but everyone (i.e. banner ads on Facebook) keeps telling me that granite is “out” and I should go for an “arctic white” finish! Should I stick with my gut, or trust Facebook to help me pick out a lasting decor?
-Save a Granite Girl’s Year
Dear SaGGY,
Thanks for posting, @SaGGY. I’m a repo bot– nice to meet you!
It has been 30 days since there have been any updates or new comments on this page. If this issue has been resolved, feel free to disregard the rest of this message. On the other hand, if you are still waiting on a patch, please:
review our contribution guide to make sure this submission meets our criteria (only verified bugs with documented features, please; no questions, commentary, or bug reports about undocumented features or unofficial plugins)
create a new issue with the latest information, including updated version details with error messages, failing tests, and a link back to the original issue. This allows GitHub to automatically create a back-reference for future visitors arriving from search engines.
Thanks so much for your help!
A Study in Keith: A live musical performance by Andrew Sorensen.
A Study in Keith: A live musical performance by Andrew Sorensen.
The first two minutes (1:56) are silent, while the performer writes the program that loops the introduction, then the rest is gold. I suggest starting at 8:08. This feels just like what you might do in Reason/Ableton, except instead of the graphical sequencer corresponding with piano keys, it's expressed in lamda functions, and instead of the playhead, he's got a the blinking caret.
Next time I write a song, maybe I'll sing with a Lisp.
Or maybe, next time I write a program, I'll code with a sequencer. We'll see.
Private Packages for Organizations
Today, npm released a set of features that I’m really excited about: Private Packages for Organizations.
This release allows teams to use private npm packages more effectively. It’s intended for businesses that manage developer teams, with varying permissions and multiple projects.
You can read the official announcement, and learn more about the product on our website.
A big part of the motivation to start a company around this project was that people using npm at work wanted to be able to manage teams and control access to private code. We got a lot of feedback from the early adopters of private packages for individuals, and even more from our beta testers who’ve been banging on this stuff for the last few months as it’s come together.
The team at npm has worked hard on this release, and we look forward to feedback and suggestions from the community. Now that it’s live, we’ll be continuing to improve and polish it as we see how more people use it. Contact us and tell us how we’re doing :)
What’s new in machine@^10.2.0
Here's what's new in [email protected]
Just published [email protected] which includes a couple of new things:
1. Recursive invocation (and maximum call stack protection)
just added support for maxRecursion in the machine runner. It's a top-level property that lets you configure the built-in maximum call stack protection now included in the machine runner (defaults to 250.)
Only works for recursion invoked via env.thisMachine(). For example:
var result = env.thisMachine({ stuff: inputs.i+1 }).execSync(); // ...
or
env.thisMachine({ stuff: inputs.i+1 }).exec({ error: exits.error, success: function (result) { // ... } });
If maxRecursion is exceeded, instead of returning the real machine instance, env.thisMachine() returns a decoy machine that always triggers its error exit with an Error instance (whose code property is "E_MAX_RECURSION")
2. Auto-timeout
This version also includes support for timeout, a top-level property configurable on a machine definition that indicates the max number of milliseconds to allow the machine to run before giving up and calling the error exit with an Error instance (.code === 'E_TIMEOUT'). Defaults to 30000 (30 seconds).
Spec for custom commands + deployment strategies in Sails.js
Currently Sails.js does not have the ability to create custom commands on the sails command line tool. Here's how we can do it.
Contents
Custom commands in Sails
sails deploy
Custom commands in Sails
example .sailsrc file
{ "commands": { "deploy": "sails-deploy" }, "deploy": { "module": "sails-deploy-azure" } }
In Sails core...
(in bin/sails.js)
When the CLI is run as sails deploy, since this is an unrecognized command, Sails should load base config using the rc module (see bin/sails-generate.js for an example). This will load any relevant config from the .sailsrc file(s) which apply to the current app.
Assuming we have a .sailsrc like the one above, there will be new commands and deploy namespaces in the app's configuration object. So we'd be able to access:
configObjectReturnedByRC.deploy.module // => 'sails-deploy-azure' configObjectReturnedByRC.commands.deploy // => 'sails-deploy'
Sails core should then check to see if commands exist, and if so, loop through each one and see if there is a command matching what the user is trying to do (e.g. "deploy"). If no match is found, cli does exactly what it does today (show usage instructions). But if a match is found, then the CLI requires the configured module ("sails-deploy") from the context of the current sails project's "appPath" (configObjectReturnedByRC.appPath). For example, something like this (warning: I did not run or double-check this code):
var configObjectReturnedByRC = require('rc')('sails'); var matchingCommand = _.find(configObjectReturnedByRC.commands || [], function (val, key){}); if (!matchingCommand) { /*... handle this case exactly as happens currently, then return early */ return; } var pathToRequire = require('path').resolve(configObjectReturnedByRC.appPath||process.cwd(), 'node_modules', matchingCommand); var customCmd; try { customCmd = require(pathToRequire); } catch (e){ // check if this is a MODULE_NOT_FOUND error (look at e.type or e.code, can't remember) // if not, log an error message explaining that the custom command module referenced in one of the user's sailsrc files has an issue (then dump the original error stack and bail out/return early) // if so, log a nice error message explaining the command the user typed was recognized as a custom command defined in one of their sailsrc files, but it could not be required from the specified path. Then bail out (return early) } // Finally, if that all worked, run the custom command. customCmd({ config: configObjectReturnedByRC }, function (err){ if (err) { // log msg explaining deployment failed, then log the original error stack for details. return; } // log msg explaining deployment was successful. });
Note that this approach means users will need to npm install sails-noop --save in their project in order to run sails noop on the command line.
Custom command packages
Custom sails commands are npm packages that export a function w/ two arguments- options and cb (following standard node conventions).
For example, here's a very simple no-op command as an example (sails-noop). Aside from its package.json , it has only one file: index.js.
/** * Module dependences */ // ... /** * Usage: * `$ sails noop` */ module.exports = function noopCmd (options, cb) { // `options.config` is provided with the raw config that Sails core gathered by running `rc`. return cb(); };
====================================================
sails deploy
sails deploy can be implemented as a custom command as specced out above.
the sails-deploy package
The job of the sails-deploy package is to require() the configured strategy, then run it. Eventually it can end up taking responsibility for generic shared logic between deployment strategies (e.g. talking to github, pulling/pushing/tagging, etc.)- but we won't know the right things to generalize until later when we have a few different strategies to look at. For now, we'll just run the strategy.
This will be pretty similar to what we just did (which is also more or less how generators, adapters, and hooks work). In the deploy object in the sailsrc file, the module property can be specified as any of the following: 1. an absolute path (if it starts with /) 2. a relative path from the sailsrc file (if it starts with ./ or ../), or otherwise 3. an NPM package name (in which sails will attempt to require() the deployment strategy module from the node_modules folder within the sails project's appPath).
Note that this approach means a user will need to npm install sails-deploy-noop --save in her project and configure "deploy": {"module": "sails-deploy-noop"} in her sailsrc file. Then she can run sails deploy on the command line and sails-deploy-noop will be used automatically.
sails-deploy-* strategies
The simplest no-op deploy strategy (e.g. sails-deploy-noop) is simply an npm package with a package.json and an index.js file:
/** * Module dependences */ // ... module.exports = function sailsDeployNoop(options, cb) { // `options.config` is provided with the raw config that Sails core gathered by running `rc`. return cb(); };
Again, we can get fancier here eventually as far as what happens in sails-deploy itself, but for now, a simple function is the right approach.
If anyone else is interested in getting involved on this effort, please let me know here (or on twitter @mikermcneil.) Thanks!
(this was originally proposed in this PR)
Here's what I do when I publish an NPM package.
Launching a Y Combinator-backed product
Last Wednesday, I launched an app for the first time since my company joined Y Combinator. It was huge for us: our TechCrunch article has been shared over 1,500 times, we were on the front page of Hacker News, and the ProductHunt post about Treeline has received almost 1,000 upvotes. Best of all, in the first 48 hours, we had almost 10,000 developers sign up for our app.
Since then, a lot of people have been asking about our launch strategy, and for the low-down on our numbers. Rather than responding to everyone individually, I figured I'd put together a more complete analysis for everyone to share. Here goes:
Wednesday morning, I drove up to San Francisco to chat with Kyle Russell from TechCrunch. We had just launched our developer preview of Treeline a few days before, but we still hadn't announced ourselves as a company in the Winter 2015 batch of Y Combinator. With Demo Day fast approaching, it was time.
The chat with TechCrunch went well (Kyle's a really cool guy), and about an hour later I was on my way to SFO to fly back to Austin for South by Southwest Interactive. I wasn't sure exactly when the TechCrunch article would go out, but I figured we'd have at least a few days to "batten down the hatches", so to speak.
So when the plane landed in Austin at around 9PM and my phone started going off with over 100 Slack notifications, I started to panic a little. The TechCrunch article on Treeline had gone out 18 minutes ago.
There were a few comments on the article itself, but I spent most of the next four hours glued to my phone responding to tweets. Back in California, Scott and Cody had turned our living room into a command center to send out beta invites and monitor our infrastructure, and Irl was responding to in-app questions with Intercom.
Within an hour and a half, we were on the front-page of Hacker News. I geared up for a long night. This had happened to me a few times before: when I posted the original Sails.js screencast, when I first talked about the framework on InfoQ back in 2013, when Microsoft contributed to the framework, and more recently when I taught a live Sails.js course on Platzi.
A good eight hours on the front-page of Hacker News usually drives anywhere between 1,000 and 5,000 unique visitors, depending on the time of day. That's not all that crazy, but it's important because of who the visitors are: in my opinon, the Hacker News readership is a mix of extremely vocal (and oftentimes nameless) commentors and mostly quiet but influential readers. The comments can get pretty negative sometimes, but there are usually some nice folks who will stand up for you, particularly after dark (PST).
It's important to realize that 90% of the value that comes from this sort of attention- whether it's a TechCrunch article or getting on the front-page of Hacker News or showing up at the top of subreddit- is the social media whirlwind that follows. Remember when I mentioned the 1,000-5,000 visitors from Hacker News earlier? The real number is actually much higher thanks to shares on Twitter, Facebook, LinkedIn, Reddit, and other social media websites (more like 15,000-30,000 unique visitors.)
By 11:30 Austin time (9:30 back in California), we'd seen over 2,000 new signups for the beta, and the TechCrunch article had around 500 shares. Unfortunately, Hacker News went down a few times during this stretch of the night and we lost some traction there. However, despite only being on the front page for a couple of hours, Cody's post picked up around 60 upvotes and definitely drove some virality.
As we invited more and more users to our closed beta, we ran into a memory leak which took us offline for several minutes, but we got around it temporarily by cranking ourselves up to 20 Heroku dynos. We were eventually able to resolve the issue, but around midnight, we had to triage our experimental API hosting feature. This was unfortunate, but not that big of a deal (Treeline ships with a CLI tool that you can use to develop against your app locally)
By around 1AM PST, we had around 3,000 signups, and that was just fine. I was relieved everything was working again for the people we'd let in so far. But then things got weird.
I'd heard of Product Hunt, but being rather disconnected from the mainstream Silicon Valley scene, I didn't really know much more beyond the general idea: a website that lists a bunch of product launches you can upvote every day.
But nothing could prepare me for what happened next.
In the next four hours, the ProductHunt post brought us over 4,000 new signups. And these weren't just any signups-- these were JavaScript, iOS, and Android developers! (Considering that only 0.2% of the world can write code, I can't even imagine what that number would look like if we were out of beta and launching to a wider audience of non-developers.) Crazier still, the next morning (Friday), when Product Hunt sent out the daily top 10 email, it drove 2,000 more.
While I learned a lot last week, perhaps the most surprising thing I discovered was Product Hunt. It does not drive more web traffic than "official" media channels or Hacker News... but it drives positive discussion, valid questions and actual early adopters. It brought us some of our most influential and active users from outside of the Sails.js community- people who actually use our platform.
As to "how?"...unfortunately, I have no idea. It's possible that Treeline was a fluke, or that developer tools are just a particularly good fit for the Product Hunt community (as of today, our launch is the 15th-most-upvoted post in the history of the site). But either way, I can't emphasize enough how important the platform was for us. If you're a startup founder putting together a lauch plan, you should definitely make sure you've got ProductHunt near the top of your list.
Here are some of the things I would do differently next time:
1. Special invite code for the Product Hunt community
Even though we weren't ready to allow open access to our platform, I should have thrown together a quick hack to allow people from Product Hunt to enter a special invite code which was time-boxed for 12 hours. Then we could have shared that invite code as a comment on the post and let more of the Product Hunt community access the site
2. Tweet button after signing up
We forgot to put a "Tweet" button on the "Thanks for signing up for our beta!" page until days after our launch. This could have added an additional factor of exponential growth, since every developer who signed up would have been reminded to let their friends and coworkers know about Sails.js/Treeline (we also could have put a "Tweet" button on every circuit so that new developers would know that they could share a permalink to the their app, or even down to a specific route or model. It was a feature we spent a lot of time working on, but no one even knew about it)
3. Spend more time in Product Hunt
We didn't realize how much of our growth was coming from Product Hunt until we were all about to pass out and go to sleep. If we had spent less time in Twitter and Hacker News comments, and more time granting beta access to folks on Product Hunt, we could have done even better than we did.
Anyway, hopefully reading about our launch was helpful for you. And if you're using Treeline, thanks so much for your support. Your early involvment and feedback is crucial to the success of our mission: eliminating repetitive backend development forever.
Feel free to hit me up if you have any questions.
-m
PS. if you're still waiting on your Treeline invite, please be patient-- we're still trying to keep up. If you're anxious to get started, give @treelinehq a shout on Twitter and me or someone on our team will help you cut the line.